more tests, i think most things work now?

v2
Brett 2025-02-21 16:37:05 -05:00
parent f403e8a69b
commit 30f975e165
3 changed files with 166 additions and 54 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake) include(cmake/color.cmake)
set(BLT_VERSION 4.0.21) set(BLT_VERSION 4.0.22)
set(BLT_TARGET BLT) set(BLT_TARGET BLT)

View File

@ -188,7 +188,7 @@ namespace blt::argparse
} }
}; };
using arg_meta_type_helper_t = arg_data_helper_t<bool, i8, i16, i32, i64, u8, u16, u32, u64, float, double, std::string_view>; using arg_meta_type_helper_t = arg_data_helper_t<bool, i8, i16, i32, i64, u8, u16, u32, u64, float, double, std::string>;
using arg_data_t = arg_meta_type_helper_t::variant_t; using arg_data_t = arg_meta_type_helper_t::variant_t;
template <typename T> template <typename T>
@ -376,9 +376,9 @@ namespace blt::argparse
return std::get<T>(m_data.at(key)); return std::get<T>(m_data.at(key));
} }
[[nodiscard]] std::string_view get(const std::string_view key) const [[nodiscard]] const std::string& get(const std::string_view key) const
{ {
return std::get<std::string_view>(m_data.at(key)); return std::get<std::string>(m_data.at(key));
} }
bool contains(const std::string_view key) bool contains(const std::string_view key)
@ -410,9 +410,9 @@ namespace blt::argparse
{ {
m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value)
{ {
storage.m_data.emplace(std::string{dest}, value); storage.m_data.emplace(std::string{dest}, std::string{value});
}; };
m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string_view>& values) m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string>& values)
{ {
storage.m_data.emplace(std::string{dest}, values); storage.m_data.emplace(std::string{dest}, values);
}; };
@ -426,7 +426,7 @@ namespace blt::argparse
{ {
storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t<T>::convert(value)); storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t<T>::convert(value));
}; };
m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string_view>& values) m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string>& values)
{ {
if (storage.m_data.contains(dest)) if (storage.m_data.contains(dest))
{ {
@ -451,6 +451,11 @@ namespace blt::argparse
return *this; return *this;
} }
argument_builder_t& set_flag()
{
return set_action(action_t::STORE_TRUE);
}
argument_builder_t& set_action(const action_t action) argument_builder_t& set_action(const action_t action)
{ {
m_action = action; m_action = action;
@ -474,6 +479,9 @@ namespace blt::argparse
set_nargs(0); set_nargs(0);
as_type<size_t>(); as_type<size_t>();
break; break;
case action_t::EXTEND:
set_nargs(nargs_t::ALL);
break;
case action_t::HELP: case action_t::HELP:
case action_t::VERSION: case action_t::VERSION:
set_nargs(0); set_nargs(0);
@ -553,7 +561,7 @@ namespace blt::argparse
// dest, storage, value input // dest, storage, value input
std::function<void(std::string_view, argument_storage_t&, std::string_view)> m_dest_func; std::function<void(std::string_view, argument_storage_t&, std::string_view)> m_dest_func;
// dest, storage, value input // dest, storage, value input
std::function<void(std::string_view, argument_storage_t&, const std::vector<std::string_view>& values)> m_dest_vec_func; std::function<void(std::string_view, argument_storage_t&, const std::vector<std::string>& values)> m_dest_vec_func;
}; };
class argument_positional_storage_t class argument_positional_storage_t
@ -611,6 +619,7 @@ namespace blt::argparse
std::string, Aliases>>...>, std::string, Aliases>>...>,
"Arguments must be of type string_view, convertible to string_view or be string_view constructable"); "Arguments must be of type string_view, convertible to string_view or be string_view constructable");
m_argument_builders.emplace_back(std::make_unique<argument_builder_t>()); m_argument_builders.emplace_back(std::make_unique<argument_builder_t>());
m_argument_builders.back()->set_dest(arg);
m_flag_arguments.emplace(arg, m_argument_builders.back().get()); m_flag_arguments.emplace(arg, m_argument_builders.back().get());
(m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...); (m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...);
return *m_argument_builders.back().get(); return *m_argument_builders.back().get();
@ -716,9 +725,9 @@ namespace blt::argparse
void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg);
static void handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments, static void handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
const hashset_t<std::string>& found, argument_storage_t& parsed_args, std::string_view type); const hashset_t<std::string>& found, argument_storage_t& parsed_args, std::string_view type);
static expected<std::vector<std::string_view>, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, static expected<std::vector<std::string>, std::string> consume_until_flag_or_end(argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices); hashset_t<std::string>* allowed_choices);
static std::vector<std::string_view> consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t<std::string>* allowed_choices, static std::vector<std::string> consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t<std::string>* allowed_choices,
std::string_view arg); std::string_view arg);
std::optional<std::string> m_name; std::optional<std::string> m_name;
@ -746,7 +755,7 @@ namespace blt::argparse
std::conjunction_v<std::disjunction<std::is_convertible<Aliases, std::string_view>, std::is_constructible< std::conjunction_v<std::disjunction<std::is_convertible<Aliases, std::string_view>, std::is_constructible<
std::string_view, Aliases>>...>, std::string_view, Aliases>>...>,
"Arguments must be of type string_view, convertible to string_view or be string_view constructable"); "Arguments must be of type string_view, convertible to string_view or be string_view constructable");
m_parsers.emplace(name); m_parsers.emplace(name, argument_parser_t{});
((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...);
return m_parsers[name]; return m_parsers[name];
} }

View File

@ -67,7 +67,8 @@ namespace blt::argparse
template <typename T> template <typename T>
auto ensure_is_string(T&& t) auto ensure_is_string(T&& t)
{ {
if constexpr (std::is_arithmetic_v<meta::remove_cvref_t<T>>) if constexpr (std::is_arithmetic_v<meta::remove_cvref_t<T>> && !(std::is_same_v<T, char>
|| std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>))
return std::to_string(std::forward<T>(t)); return std::to_string(std::forward<T>(t));
else else
return std::forward<T>(t); return std::forward<T>(t);
@ -82,6 +83,12 @@ namespace blt::argparse
return out; return out;
} }
template <typename... Strings>
std::vector<std::string_view> make_arguments(Strings... strings)
{
return std::vector<std::string_view>{"./program", strings...};
}
namespace detail namespace detail
{ {
// Unit Tests for class argument_string_t // Unit Tests for class argument_string_t
@ -254,7 +261,7 @@ namespace blt::argparse
argument_parser_t parser; argument_parser_t parser;
parser.add_flag("-x").as_type<int>(); parser.add_flag("-x").as_type<int>();
parser.add_flag("/y").as_type<std::string_view>(); parser.add_flag("/y").as_type<std::string>();
const std::vector<std::string> args = {"./program", "-x", "10", "!z", "/y", "value"}; const std::vector<std::string> args = {"./program", "-x", "10", "!z", "/y", "value"};
try try
@ -415,6 +422,102 @@ namespace blt::argparse
std::cout << "Success: test_nargs_all_at_least_one\n"; std::cout << "Success: test_nargs_all_at_least_one\n";
} }
void run_combined_flag_test()
{
std::cout << "[Running Test: run_combined_flag_test]\n";
argument_parser_t parser;
parser.add_flag("-a").set_action(action_t::STORE_TRUE);
parser.add_flag("--deep").set_action(action_t::STORE_FALSE);
parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50);
parser.add_flag("--append").set_action(action_t::APPEND).as_type<int>();
parser.add_flag("--required").set_required(true);
parser.add_flag("--default").set_default("I am a default value");
parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5);
parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10);
parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15);
parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20);
parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25);
parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type<float>();
const auto a1 = make_arguments("-a", "--required", "hello");
const auto r1 = parser.parse(a1);
BLT_ASSERT(r1.get<bool>("-a") == true && "Flag '-a' should store true");
BLT_ASSERT(r1.get<std::string>("--default") == "I am a default value" && "Flag '--default' should store default value");
BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'");
const auto a2 = make_arguments("-a", "--deep", "--required", "soft");
const auto r2 = parser.parse(a2);
BLT_ASSERT(r2.get<bool>("-a") == true && "Flag '-a' should store true");
BLT_ASSERT(r2.get<bool>("--deep") == false && "Flag '--deep' should store false");
BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'");
const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e");
const auto r3 = parser.parse(a3);
BLT_ASSERT((r3.get<std::vector<int>>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}");
BLT_ASSERT(r3.get<int>("-b") == 50 && "Combined flag should store const of 50");
const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f");
const auto r4 = parser.parse(a4);
BLT_ASSERT(
(r4.get<std::vector<int>>("test") == std::vector{25, 5, 10, 15, 20}) &&
"Expected test vector to be filled with all arguments in order of flags");
BLT_ASSERT(
(r4.get<std::vector<float>>("wow") == std::vector<float>{10, 12.05, 68.11, 100.00, 200532}) &&
"Extend vector expected to contain all elements");
std::cout << "Success: run_combined_flag_test\n";
}
void run_subparser_test()
{
std::cout << "[Running Test: run_subparser_test]\n";
argument_parser_t parser;
parser.add_flag("--open").set_flag();
auto& subparser = parser.add_subparser("mode");
auto& n1 = subparser.add_parser("n1");
n1.add_flag("--silly").set_flag();
n1.add_positional("path");
auto& n2 = subparser.add_parser("n2");
n2.add_flag("--crazy").set_flag();
n2.add_positional("path");
n2.add_positional("output");
auto& n3 = subparser.add_parser("n3");
n3.add_flag("--deep").set_flag();
const auto a1 = make_arguments("n1", "--silly");
try
{
parser.parse(a1);
BLT_ASSERT(false && "Subparser should throw an error when positional not supplied");
}
catch (...)
{
}
const auto a2 = make_arguments("--open");
try
{
parser.parse(a2);
BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied");
}
catch (...)
{
}
const auto a3 = make_arguments("n1", "--silly", "path");
const auto r3 = parser.parse(a3);
BLT_ASSERT(r3.get<bool>("--open") == false && "Flag '--open' should default to false");
BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'");
BLT_ASSERT(r3.get("path") == "path" && "Subparser path should be 'path'");
std::cout << "Success: run_subparser_test\n";
}
void run_argparse_flag_tests() void run_argparse_flag_tests()
{ {
test_single_flag_prefixes(); test_single_flag_prefixes();
@ -422,6 +525,8 @@ namespace blt::argparse
test_compound_flags(); test_compound_flags();
test_combination_of_valid_and_invalid_flags(); test_combination_of_valid_and_invalid_flags();
test_flags_with_different_actions(); test_flags_with_different_actions();
run_combined_flag_test();
run_subparser_test();
} }
void run_all_nargs_tests() void run_all_nargs_tests()
@ -488,7 +593,7 @@ namespace blt::argparse
for (auto& [key, subparser] : m_subparsers) for (auto& [key, subparser] : m_subparsers)
{ {
auto [parsed_subparser, storage] = subparser.parse(consumer); auto [parsed_subparser, storage] = subparser.parse(consumer);
storage.m_data.emplace(std::string{key}, detail::arg_data_t{parsed_subparser.get_argument()}); storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}});
parsed_args.add(storage); parsed_args.add(storage);
} }
@ -623,7 +728,7 @@ namespace blt::argparse
if (argc != 0) if (argc != 0)
throw detail::unexpected_argument_error( throw detail::unexpected_argument_error(
make_string("Argument '", arg, "'s action is append const but takes in arguments.")); make_string("Argument '", arg, "'s action is append const but takes in arguments."));
if (flag->m_const_value) if (!flag->m_const_value)
{ {
throw detail::missing_value_error( throw detail::missing_value_error(
make_string("Append const chosen as an action but const value not provided for argument '", arg, make_string("Append const chosen as an action but const value not provided for argument '", arg,
@ -632,40 +737,38 @@ namespace blt::argparse
if (parsed_args.contains(dest)) if (parsed_args.contains(dest))
{ {
auto& data = parsed_args.m_data[dest]; auto& data = parsed_args.m_data[dest];
auto visitor = detail::arg_meta_type_helper_t::make_visitor( std::visit(detail::arg_meta_type_helper_t::make_visitor(
[arg](auto& primitive) [arg](auto& primitive)
{ {
throw detail::type_error(make_string( throw detail::type_error(make_string(
"Invalid type for argument '", arg, "' expected list type, found '", "Invalid type for argument '", arg, "' expected list type, found '",
blt::type_string<decltype(primitive)>(), "' with value ", primitive)); blt::type_string<decltype(primitive)>(), "' with value ", primitive));
}, },
[&flag, arg](auto& vec) [&flag, arg](auto& vec)
{ {
using type = typename meta::remove_cvref_t<decltype(vec)>::value_type; using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
if (!std::holds_alternative<type>(*flag->m_const_value)) if (!std::holds_alternative<type>(*flag->m_const_value))
{ {
throw detail::type_error(make_string("Constant value for argument '", arg, throw detail::type_error(make_string("Constant value for argument '", arg,
"' type doesn't match values already present! Expected to be of type '", "' type doesn't match values already present! Expected to be of type '",
blt::type_string<type>(), "'!")); blt::type_string<type>(), "'!"));
} }
vec.push_back(std::get<type>(*flag->m_const_value)); vec.push_back(std::get<type>(*flag->m_const_value));
}); }), data);
std::visit(visitor, data);
} }
else else
{ {
auto visitor = detail::arg_meta_type_helper_t::make_visitor( std::visit(detail::arg_meta_type_helper_t::make_visitor(
[&parsed_args, &dest](auto& primitive) [&parsed_args, &dest](auto& primitive)
{ {
std::vector<meta::remove_cvref_t<decltype(primitive)>> vec; std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
vec.push_back(primitive); vec.emplace_back(primitive);
parsed_args.m_data.insert({dest, std::move(vec)}); parsed_args.m_data.emplace(dest, std::move(vec));
}, },
[](auto&) [](auto&)
{ {
throw detail::type_error("Append const should not be a list type!"); throw detail::type_error("Append const should not be a list type!");
}); }), *flag->m_const_value);
std::visit(visitor, *flag->m_const_value);
} }
break; break;
case action_t::STORE_CONST: case action_t::STORE_CONST:
@ -826,10 +929,10 @@ namespace blt::argparse
} }
} }
expected<std::vector<std::string_view>, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, expected<std::vector<std::string>, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices) hashset_t<std::string>* allowed_choices)
{ {
std::vector<std::string_view> args; std::vector<std::string> args;
while (consumer.can_consume() && !consumer.peek().is_flag()) while (consumer.can_consume() && !consumer.peek().is_flag())
{ {
if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
@ -849,11 +952,11 @@ namespace blt::argparse
return args; return args;
} }
std::vector<std::string_view> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, std::vector<std::string> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices, hashset_t<std::string>* allowed_choices,
const std::string_view arg) const std::string_view arg)
{ {
std::vector<std::string_view> args; std::vector<std::string> args;
for (i32 i = 0; i < argc; ++i) for (i32 i = 0; i < argc; ++i)
{ {
if (!consumer.can_consume()) if (!consumer.can_consume())
@ -882,7 +985,7 @@ namespace blt::argparse
"' is not a valid choice for argument '", arg, "' is not a valid choice for argument '", arg,
"'! Expected one of ", valid_choices)); "'! Expected one of ", valid_choices));
} }
args.push_back(consumer.consume().get_argument()); args.push_back(std::string{consumer.consume().get_argument()});
} }
if (args.size() != static_cast<size_t>(argc)) if (args.size() != static_cast<size_t>(argc))
{ {