diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3ae9eac..81e6256 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake)
-set(BLT_VERSION 4.0.11)
+set(BLT_VERSION 4.0.12)
set(BLT_TARGET BLT)
diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h
new file mode 100644
index 0000000..d811aa5
--- /dev/null
+++ b/include/blt/meta/type_traits.h
@@ -0,0 +1,31 @@
+#pragma once
+/*
+ * Copyright (C) 2024 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BLT_META_TYPE_TRAITS_H
+#define BLT_META_TYPE_TRAITS_H
+
+#include
+
+namespace blt::meta {
+
+ template
+ using remove_cvref_t = std::remove_volatile_t>>;
+
+}
+
+#endif // BLT_META_TYPE_TRAITS_H
diff --git a/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h
new file mode 100644
index 0000000..e72fc0f
--- /dev/null
+++ b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h
@@ -0,0 +1,484 @@
+#pragma once
+/*
+ * Copyright (C) 2024 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BLT_PARSE_ARGPARSE_V2_H
+#define BLT_PARSE_ARGPARSE_V2_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace blt::argparse
+{
+ class argument_string_t;
+ class argument_consumer_t;
+ class argument_parser_t;
+ class argument_subparser_t;
+ class argument_builder_t;
+ class argument_storage_t;
+
+ enum class action_t
+ {
+ STORE,
+ STORE_CONST,
+ STORE_TRUE,
+ STORE_FALSE,
+ APPEND,
+ APPEND_CONST,
+ COUNT,
+ HELP,
+ VERSION,
+ EXTEND,
+ SUBCOMMAND
+ };
+
+ enum class nargs_t
+ {
+ IF_POSSIBLE,
+ ALL,
+ ALL_AT_LEAST_ONE
+ };
+
+ using nargs_v = std::variant;
+
+ namespace detail
+ {
+ class bad_flag final : public std::runtime_error
+ {
+ public:
+ explicit bad_flag(const std::string& message): std::runtime_error(message)
+ {
+ }
+
+ explicit bad_flag(const char* message): std::runtime_error(message)
+ {
+ }
+ };
+
+ class missing_argument_error final : public std::runtime_error
+ {
+ public:
+ explicit missing_argument_error(const std::string& message): std::runtime_error(message)
+ {
+ }
+ }
+
+ class subparse_error final : public std::exception
+ {
+ public:
+ explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings):
+ m_found_string(found_string),
+ m_allowed_strings(std::move(allowed_strings))
+ {
+ }
+
+ [[nodiscard]] const std::vector>& get_allowed_strings() const
+ {
+ return m_allowed_strings;
+ }
+
+ [[nodiscard]] std::string_view get_found_string() const
+ {
+ return m_found_string;
+ }
+
+ [[nodiscard]] std::string error_string() const
+ {
+ std::string message = "Subparser Error: ";
+ message += m_found_string;
+ message += " is not a valid command. Allowed commands are: {";
+ for (const auto [i, allowed_string] : enumerate(m_allowed_strings))
+ {
+ if (allowed_string.size() > 1)
+ message += '[';
+ for (const auto [j, alias] : enumerate(allowed_string))
+ {
+ message += alias;
+ if (j < alias.size() - 2)
+ message += ", ";
+ else if (j < alias.size())
+ message += ", or ";
+ }
+ if (allowed_string.size() > 1)
+ message += ']';
+ if (i != m_allowed_strings.size() - 1)
+ message += ' ';
+ }
+ message += "}";
+ return message;
+ }
+
+ [[nodiscard]] const char* what() const override
+ {
+ return "Please use error_string() method instead of what(). This exception should *always* be caught!";
+ }
+
+ private:
+ std::string_view m_found_string;
+ std::vector> m_allowed_strings;
+ };
+
+ template
+ struct arg_data_helper_t
+ {
+ using arg_primitive_data_t = std::variant;
+ using arg_list_data_t = std::variant...>;
+ };
+
+ using data_helper_t = arg_data_helper_t;
+
+ using arg_primitive_data_t = data_helper_t::arg_primitive_data_t;
+ using arg_list_data_t = data_helper_t::arg_list_data_t;
+ using arg_data_t = std::variant;
+
+ template
+ struct arg_type_t
+ {
+ static T convert(const std::string_view value)
+ {
+ static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v,
+ "Type must be arithmetic, string_view or string!");
+ const std::string temp{value};
+
+ if constexpr (std::is_same_v)
+ {
+ return std::stof(temp);
+ }
+ else if constexpr (std::is_same_v)
+ {
+ return std::stod(temp);
+ }
+ else if constexpr (std::is_unsigned_v)
+ {
+ return static_cast(std::stoull(temp));
+ }
+ else if constexpr (std::is_signed_v)
+ {
+ return static_cast(std::stoll(temp));
+ }
+ else if constexpr (std::is_same_v)
+ {
+ return value;
+ }
+ else if constexpr (std::is_same_v)
+ {
+ return std::string(value);
+ }
+ BLT_UNREACHABLE;
+ }
+ };
+
+ void test();
+ }
+
+ class argument_string_t
+ {
+ public:
+ explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input),
+ allowed_flag_prefix(&allowed_flag_prefix)
+ {
+ if (input == nullptr)
+ throw detail::bad_flag("Argument cannot be null!");
+ process_argument();
+ }
+
+ [[nodiscard]] std::string_view get_flag() const
+ {
+ return m_flag_section;
+ }
+
+ [[nodiscard]] std::string_view get_name() const
+ {
+ return m_name_section;
+ }
+
+ [[nodiscard]] std::string_view value() const
+ {
+ return get_name();
+ }
+
+ [[nodiscard]] bool is_flag() const
+ {
+ return !m_flag_section.empty();
+ }
+
+ [[nodiscard]] std::string_view get_argument() const
+ {
+ return m_argument;
+ }
+
+ private:
+ void process_argument()
+ {
+ size_t start = 0;
+ for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++)
+ {
+ }
+
+ m_flag_section = {m_argument.data(), start};
+ m_name_section = {m_argument.data() + start, m_argument.size() - start};
+ }
+
+ std::string_view m_argument;
+ std::string_view m_flag_section;
+ std::string_view m_name_section;
+ const hashset_t* allowed_flag_prefix;
+ };
+
+ class argument_consumer_t
+ {
+ public:
+ explicit argument_consumer_t(const span& args): m_args(args)
+ {
+ }
+
+ [[nodiscard]] argument_string_t peek(const i32 offset = 0) const
+ {
+ return m_args[m_forward_index + offset];
+ }
+
+ argument_string_t consume()
+ {
+ return m_args[m_forward_index++];
+ }
+
+ [[nodiscard]] i32 position() const
+ {
+ return m_forward_index;
+ }
+
+ [[nodiscard]] i32 remaining() const
+ {
+ return static_cast(m_args.size()) - m_forward_index;
+ }
+
+ [[nodiscard]] bool has_next(const i32 offset = 0) const
+ {
+ return (offset + m_forward_index) < m_args.size();
+ }
+
+ private:
+ span m_args;
+ i32 m_forward_index = 0;
+ };
+
+ class argument_storage_t
+ {
+ friend argument_parser_t;
+ friend argument_subparser_t;
+ friend argument_builder_t;
+
+ public:
+ template
+ const T& get(const std::string_view key)
+ {
+ return std::get(m_data[key]);
+ }
+
+ std::string_view get(const std::string_view key)
+ {
+ return std::get(m_data[key]);
+ }
+
+ bool contains(const std::string_view key)
+ {
+ return m_data.find(key) != m_data.end();
+ }
+
+ private:
+ hashmap_t m_data;
+ };
+
+ class argument_builder_t
+ {
+ friend argument_parser_t;
+
+ public:
+ argument_builder_t()
+ {
+ dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value)
+ {
+ storage.m_data[dest] = value;
+ };
+ }
+
+ template
+ argument_builder_t& as_type()
+ {
+ dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value)
+ {
+ storage.m_data[dest] = detail::arg_type_t::convert(value);
+ };
+ return *this;
+ }
+
+ private:
+ action_t action = action_t::STORE;
+ bool required = false; // do we require this argument to be provided as an argument?
+ nargs_v nargs = 1; // number of arguments to consume
+ std::optional metavar; // variable name to be used in the help string
+ std::optional help; // help string to be used in the help string
+ std::optional> choices; // optional allowed choices for this argument
+ std::optional default_value;
+ std::optional const_value;
+ // dest, storage, value input
+ std::function dest_func;
+ };
+
+ class argument_parser_t
+ {
+ friend argument_subparser_t;
+
+ public:
+ explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {},
+ const std::optional description = {}, const std::optional epilogue = {}):
+ m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue)
+ {
+ }
+
+ template
+ argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases)
+ {
+ static_assert(
+ std::conjunction_v, std::is_constructible<
+ std::string_view, Aliases>>...>,
+ "Arguments must be of type string_view, convertible to string_view or be string_view constructable");
+ m_argument_builders.emplace_back();
+ m_flag_arguments[arg] = &m_argument_builders.back();
+ ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...);
+ return m_argument_builders.back();
+ }
+
+ argument_builder_t& add_positional(const std::string_view arg)
+ {
+ m_argument_builders.emplace_back();
+ m_positional_arguments[arg] = &m_argument_builders.back();
+ return m_argument_builders.back();
+ }
+
+ argument_subparser_t& add_subparser(std::string_view dest);
+
+ void parse(argument_consumer_t& consumer); // NOLINT
+
+ void print_help();
+
+ argument_parser_t& set_name(const std::string_view name)
+ {
+ m_name = name;
+ return *this;
+ }
+
+ argument_parser_t& set_usage(const std::string_view usage)
+ {
+ m_usage = usage;
+ return *this;
+ }
+
+ [[nodiscard]] const std::optional& get_usage() const
+ {
+ return m_usage;
+ }
+
+ argument_parser_t& set_description(const std::string_view description)
+ {
+ m_description = description;
+ return *this;
+ }
+
+ [[nodiscard]] const std::optional& get_description() const
+ {
+ return m_description;
+ }
+
+ argument_parser_t& set_epilogue(const std::string_view epilogue)
+ {
+ m_epilogue = epilogue;
+ return *this;
+ }
+
+ [[nodiscard]] const std::optional& get_epilogue() const
+ {
+ return m_epilogue;
+ }
+
+ private:
+ std::optional m_name;
+ std::optional m_usage;
+ std::optional m_description;
+ std::optional m_epilogue;
+ std::vector> m_subparsers;
+ std::vector m_argument_builders;
+ hashmap_t m_flag_arguments;
+ hashmap_t m_positional_arguments;
+ };
+
+ class argument_subparser_t
+ {
+ public:
+ explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent)
+ {
+ }
+
+ template
+ argument_parser_t& add_parser(const std::string_view name, Aliases... aliases)
+ {
+ static_assert(
+ std::conjunction_v, std::is_constructible<
+ std::string_view, Aliases>>...>,
+ "Arguments must be of type string_view, convertible to string_view or be string_view constructable");
+ m_parsers.emplace(name);
+ ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...);
+ return m_parsers[name];
+ }
+
+
+ /**
+ * Parses the next argument using the provided argument consumer.
+ *
+ * This function uses an argument consumer to extract and process the next argument.
+ * If the argument is a flag or if it cannot be matched against the available parsers,
+ * an exception is thrown.
+ *
+ * @param consumer Reference to an argument_consumer_t object, which handles argument parsing.
+ * The consumer provides the next argument to be parsed.
+ *
+ * @throws detail::subparse_error If the argument is a flag or does not match any known parser.
+ */
+ argument_string_t parse(argument_consumer_t& consumer); // NOLINT
+
+ private:
+ [[nodiscard]] std::vector> get_allowed_strings() const;
+
+ const argument_parser_t* m_parent;
+ hashmap_t m_parsers;
+ hashmap_t m_aliases;
+ };
+}
+
+#endif //BLT_PARSE_ARGPARSE_V2_H
diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h
index c5f53cf..bb35c98 100644
--- a/include/blt/parse/argparse_v2.h
+++ b/include/blt/parse/argparse_v2.h
@@ -134,39 +134,17 @@ namespace blt::argparse
using arg_t = meta::arg_helper;
using arg_vec_t = meta::arg_helper...>;
- template
- static auto make_lists_only_visitor_per_object_in_vec(const PerObjectAction& action)
+ template
+ static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action)
{
return lambda_visitor{
- invalid_option_lambda,
- ([&action](std::vector arg_vec)
+ ([&primitive_action](Args& arg)
{
- for (const auto& arg : arg_vec)
- action(arg_vec, arg);
- })...
- };
- }
-
- template
- static auto make_lists_only_visitor_on_vec(const DefaultAction& action)
- {
- return lambda_visitor{
- invalid_option_lambda...,
- ([&action](std::vector arg_vec)
+ return primitive_action(arg);
+ })...,
+ ([&list_action](std::vector& arg_vec)
{
- action(arg_vec);
- })...
- };
- }
-
- template
- static auto make_reject_lists_visitor_t(const DefaultAction& action)
- {
- return lambda_visitor{
- invalid_option_lambda...>,
- ([&action](Args arg)
- {
- action(arg);
+ return list_action(arg_vec);
})...
};
}
diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp
index 5ea7735..bcaf666 100644
--- a/src/blt/parse/argparse_v2.cpp
+++ b/src/blt/parse/argparse_v2.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
namespace blt::argparse
{
@@ -318,10 +319,12 @@ namespace blt::argparse
switch (flag->m_action)
{
case action_t::STORE:
+ flag->m_dest_func(dest, parsed_args, args.front());
break;
case action_t::APPEND:
case action_t::EXTEND:
{
+
break;
}
case action_t::APPEND_CONST:
@@ -334,17 +337,44 @@ namespace blt::argparse
if (parsed_args.contains(dest))
{
auto& data = parsed_args.m_data[dest];
- if (data.index() != flag->m_const_value->index())
- {
- std::cerr << "Constant value for flag '" << arg << "' type doesn't values already present!" << std::endl;
- std::exit(1);
- }
- auto visitor = detail::arg_meta_type_helper_t::make_lists_only_visitor_on_vec([&flag](auto& vec)
- {
- vec.push_back(std::get>::value_type>(*flag->m_const_value));
- });
+ auto visitor = detail::arg_meta_type_helper_t::make_visitor(
+ [arg](auto& primitive)
+ {
+ std::cerr << "Invalid type - '" << arg << "' expected list type, found '"
+ << blt::type_string() << "' with value " << primitive << std::endl;
+ std::exit(1);
+ },
+ [&flag, arg](auto& vec)
+ {
+ using type = typename meta::remove_cvref_t::value_type;
+ if (!std::holds_alternative(*flag->m_const_value))
+ {
+ std::cerr << "Constant value for flag '" << arg <<
+ "' type doesn't match values already present! Expected to be of type '" <<
+ blt::type_string() << "'!" << std::endl;
+ std::exit(1);
+ }
+ vec.push_back(std::get(*flag->m_const_value));
+ });
std::visit(visitor, data);
}
+ else
+ {
+ auto visitor = detail::arg_meta_type_helper_t::make_visitor(
+ [&flag, &parsed_args, &dest](auto& primitive)
+ {
+ std::vector> vec;
+ vec.push_back(primitive);
+ parsed_args.m_data.insert({dest, std::move(vec)});
+ },
+ [](auto&)
+ {
+ std::cerr << "Append const should not be a list type!" << std::endl;
+ std::exit(1);
+ });
+ std::visit(visitor, *flag->m_const_value);
+ }
+ break;
case action_t::STORE_CONST:
std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl;
print_usage();
@@ -358,7 +388,31 @@ namespace blt::argparse
print_usage();
std::exit(1);
case action_t::COUNT:
- parsed_args.m_data.insert({dest, args.size()});
+ if (parsed_args.m_data.contains(dest))
+ {
+ auto visitor = detail::arg_meta_type_helper_t::make_visitor(
+ [&args](auto& primitive) -> detail::arg_data_t
+ {
+ using type = meta::remove_cvref_t;
+ if constexpr (std::is_convertible_v)
+ {
+ return primitive + static_cast(args.size());
+ } else
+ {
+ std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl;
+ std::exit(1);
+ }
+ },
+ [](auto&) -> detail::arg_data_t
+ {
+ std::cerr << "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)";
+ std::exit(1);
+ }
+ );
+ parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]);
+ }
+ else
+ parsed_args.m_data.insert({dest, args.size()});
break;
case action_t::HELP:
print_help();