feat: introduce help. auto add help through parser generation like another action.

This commit is contained in:
2026-03-16 18:45:16 +04:00
parent 3a8e919ad1
commit a8b7078949
5 changed files with 110 additions and 6 deletions

View File

@@ -1,23 +1,72 @@
#pragma once #pragma once
#include <list>
#include <optional>
#include <type_traits>
#ifndef ARGUMENT_PARSER_HPP #ifndef ARGUMENT_PARSER_HPP
#define ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP
#include <any> #include <any>
#include <atomic> #include <atomic>
#include <base_convention.hpp> #include <base_convention.hpp>
#include <functional> #include <functional>
#include <initializer_list> #include <initializer_list>
#include <list>
#include <memory> #include <memory>
#include <optional>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <thread>
#include <traits.hpp> #include <traits.hpp>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
namespace argument_parser { namespace argument_parser {
namespace internal::atomic {
template <typename T> class copyable_atomic {
public:
copyable_atomic() : value(std::make_shared<std::atomic<T>>()) {}
copyable_atomic(T desired) : value(std::make_shared<std::atomic<T>>(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<std::atomic<T>> value;
};
} // namespace internal::atomic
class action_base { class action_base {
public: public:
virtual ~action_base() = default; virtual ~action_base() = default;
@@ -127,6 +176,13 @@ namespace argument_parser {
} }
} // namespace helpers } // 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 { class base_parser {
public: public:
template <typename T> template <typename T>
@@ -177,6 +233,18 @@ namespace argument_parser {
std::string program_name; std::string program_name;
std::vector<std::string> parsed_arguments; std::vector<std::string> parsed_arguments;
void reset_current_conventions() {
_current_conventions = {};
}
void current_conventions(std::initializer_list<conventions::convention const *const> convention_types) {
_current_conventions = convention_types;
}
[[nodiscard]] std::initializer_list<conventions::convention const *const> current_conventions() const {
return _current_conventions;
}
private: private:
void assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const; 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); 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<std::string, int> long_arguments; std::unordered_map<std::string, int> long_arguments;
std::unordered_map<int, std::string> reverse_long_arguments; std::unordered_map<int, std::string> reverse_long_arguments;
std::initializer_list<conventions::convention const *const> _current_conventions;
internal::atomic::copyable_atomic<std::thread::id> creation_thread_id = std::this_thread::get_id();
std::list<std::function<void(base_parser const &)>> on_complete_events; std::list<std::function<void(base_parser const &)>> on_complete_events;
friend class linux_parser; friend class linux_parser;

View File

@@ -77,9 +77,8 @@ namespace argument_parser::v2 {
return base::get_optional<T>(arg); return base::get_optional<T>(arg);
} }
void on_complete(std::function<void(argument_parser::base_parser const &)> const &action) { using argument_parser::base_parser::display_help;
base::on_complete(action); using argument_parser::base_parser::on_complete;
}
protected: protected:
void set_program_name(std::string p) { void set_program_name(std::string p) {
@@ -90,6 +89,9 @@ namespace argument_parser::v2 {
return base::parsed_arguments; return base::parsed_arguments;
} }
using argument_parser::base_parser::current_conventions;
using argument_parser::base_parser::reset_current_conventions;
private: private:
template <bool IsTyped, typename ActionType, typename T, typename ArgsMap> template <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
void add_argument_impl(ArgsMap const &argument_pairs) { void add_argument_impl(ArgsMap const &argument_pairs) {

View File

@@ -13,6 +13,7 @@ namespace argument_parser {
class windows_parser : public v2::base_parser { class windows_parser : public v2::base_parser {
public: public:
windows_parser(); windows_parser();
using base_parser::display_help;
}; };
} // namespace v2 } // namespace v2
} // namespace argument_parser } // namespace argument_parser

View File

@@ -2,6 +2,18 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <thread>
class deferred_exec {
public:
deferred_exec(std::function<void()> const &func) : func(func) {}
~deferred_exec() {
func();
}
private:
std::function<void()> func;
};
bool contains(std::unordered_map<std::string, int> const &map, std::string const &key) { bool contains(std::unordered_map<std::string, int> const &map, std::string const &key) {
return map.find(key) != map.end(); return map.find(key) != map.end();
@@ -101,6 +113,13 @@ namespace argument_parser {
} }
void base_parser::handle_arguments(std::initializer_list<conventions::convention const *const> convention_types) { void base_parser::handle_arguments(std::initializer_list<conventions::convention const *const> 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) { for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) {
std::stringstream error_stream; std::stringstream error_stream;
bool arg_correctly_handled = false; bool arg_correctly_handled = false;

View File

@@ -1,6 +1,9 @@
#ifdef _WIN32 #ifdef _WIN32
#include "windows_parser.hpp" #include "windows_parser.hpp"
#include "argument_parser.hpp"
#include "parser_v2.hpp"
#include <Windows.h> #include <Windows.h>
#include <iostream> #include <iostream>
@@ -96,6 +99,14 @@ namespace argument_parser::v2 {
windows_parser::windows_parser() { 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); }); [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 } // namespace argument_parser::v2