Merge pull request #3 from sametersoylu/v3/help

V3/help
This commit is contained in:
Abdüssamet ERSOYLU
2026-03-16 18:47:09 +04:00
committed by GitHub
10 changed files with 170 additions and 15 deletions

View File

@@ -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})
add_library(argument_parser ${SRC_FILES})
include(GNUInstallDirs)
target_include_directories(argument_parser PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers/parser>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers/conventions>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers/conventions/implementations>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers/parser/platform_headers>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/headers/parser/parsing_traits>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/parser>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/conventions>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/conventions/implementations>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/parser/platform_headers>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/parser/parsing_traits>
)
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)

View File

@@ -1,23 +1,72 @@
#pragma once
#include <list>
#include <optional>
#include <type_traits>
#ifndef ARGUMENT_PARSER_HPP
#define ARGUMENT_PARSER_HPP
#include <any>
#include <atomic>
#include <base_convention.hpp>
#include <functional>
#include <initializer_list>
#include <list>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <thread>
#include <traits.hpp>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
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 {
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 <typename T>
@@ -177,6 +233,18 @@ namespace argument_parser {
std::string program_name;
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:
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<std::string, int> 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;
friend class linux_parser;

View File

@@ -77,9 +77,8 @@ namespace argument_parser::v2 {
return base::get_optional<T>(arg);
}
void on_complete(std::function<void(argument_parser::base_parser const &)> 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 <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
void add_argument_impl(ArgsMap const &argument_pairs) {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -2,6 +2,18 @@
#include <iostream>
#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) {
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) {
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;

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,9 @@
#ifdef _WIN32
#include "windows_parser.hpp"
#include "argument_parser.hpp"
#include "parser_v2.hpp"
#include <Windows.h>
#include <iostream>
@@ -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