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)
include(cmake/color.cmake)
set(BLT_VERSION 4.0.21)
set(BLT_VERSION 4.0.22)
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;
template <typename T>
@ -376,9 +376,9 @@ namespace blt::argparse
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)
@ -410,9 +410,9 @@ namespace blt::argparse
{
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);
};
@ -426,7 +426,7 @@ namespace blt::argparse
{
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))
{
@ -451,6 +451,11 @@ namespace blt::argparse
return *this;
}
argument_builder_t& set_flag()
{
return set_action(action_t::STORE_TRUE);
}
argument_builder_t& set_action(const action_t action)
{
m_action = action;
@ -474,6 +479,9 @@ namespace blt::argparse
set_nargs(0);
as_type<size_t>();
break;
case action_t::EXTEND:
set_nargs(nargs_t::ALL);
break;
case action_t::HELP:
case action_t::VERSION:
set_nargs(0);
@ -553,7 +561,7 @@ namespace blt::argparse
// dest, storage, value input
std::function<void(std::string_view, argument_storage_t&, std::string_view)> m_dest_func;
// 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
@ -611,6 +619,7 @@ namespace blt::argparse
std::string, Aliases>>...>,
"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.back()->set_dest(arg);
m_flag_arguments.emplace(arg, m_argument_builders.back().get());
(m_flag_arguments.emplace(aliases, 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);
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);
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);
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::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::string_view, Aliases>>...>,
"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]), ...);
return m_parsers[name];
}

View File

@ -67,7 +67,8 @@ namespace blt::argparse
template <typename 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));
else
return std::forward<T>(t);
@ -82,6 +83,12 @@ namespace blt::argparse
return out;
}
template <typename... Strings>
std::vector<std::string_view> make_arguments(Strings... strings)
{
return std::vector<std::string_view>{"./program", strings...};
}
namespace detail
{
// Unit Tests for class argument_string_t
@ -254,7 +261,7 @@ namespace blt::argparse
argument_parser_t parser;
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"};
try
@ -415,6 +422,102 @@ namespace blt::argparse
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()
{
test_single_flag_prefixes();
@ -422,6 +525,8 @@ namespace blt::argparse
test_compound_flags();
test_combination_of_valid_and_invalid_flags();
test_flags_with_different_actions();
run_combined_flag_test();
run_subparser_test();
}
void run_all_nargs_tests()
@ -488,7 +593,7 @@ namespace blt::argparse
for (auto& [key, subparser] : m_subparsers)
{
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);
}
@ -623,7 +728,7 @@ namespace blt::argparse
if (argc != 0)
throw detail::unexpected_argument_error(
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(
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))
{
auto& data = parsed_args.m_data[dest];
auto visitor = detail::arg_meta_type_helper_t::make_visitor(
[arg](auto& primitive)
{
throw detail::type_error(make_string(
"Invalid type for argument '", arg, "' expected list type, found '",
blt::type_string<decltype(primitive)>(), "' with value ", primitive));
},
[&flag, arg](auto& vec)
{
using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
if (!std::holds_alternative<type>(*flag->m_const_value))
{
throw detail::type_error(make_string("Constant value for argument '", arg,
"' type doesn't match values already present! Expected to be of type '",
blt::type_string<type>(), "'!"));
}
vec.push_back(std::get<type>(*flag->m_const_value));
});
std::visit(visitor, data);
std::visit(detail::arg_meta_type_helper_t::make_visitor(
[arg](auto& primitive)
{
throw detail::type_error(make_string(
"Invalid type for argument '", arg, "' expected list type, found '",
blt::type_string<decltype(primitive)>(), "' with value ", primitive));
},
[&flag, arg](auto& vec)
{
using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
if (!std::holds_alternative<type>(*flag->m_const_value))
{
throw detail::type_error(make_string("Constant value for argument '", arg,
"' type doesn't match values already present! Expected to be of type '",
blt::type_string<type>(), "'!"));
}
vec.push_back(std::get<type>(*flag->m_const_value));
}), data);
}
else
{
auto visitor = detail::arg_meta_type_helper_t::make_visitor(
[&parsed_args, &dest](auto& primitive)
{
std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
vec.push_back(primitive);
parsed_args.m_data.insert({dest, std::move(vec)});
},
[](auto&)
{
throw detail::type_error("Append const should not be a list type!");
});
std::visit(visitor, *flag->m_const_value);
std::visit(detail::arg_meta_type_helper_t::make_visitor(
[&parsed_args, &dest](auto& primitive)
{
std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
vec.emplace_back(primitive);
parsed_args.m_data.emplace(dest, std::move(vec));
},
[](auto&)
{
throw detail::type_error("Append const should not be a list type!");
}), *flag->m_const_value);
}
break;
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,
hashset_t<std::string>* allowed_choices)
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)
{
std::vector<std::string_view> args;
std::vector<std::string> args;
while (consumer.can_consume() && !consumer.peek().is_flag())
{
if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
@ -849,11 +952,11 @@ namespace blt::argparse
return args;
}
std::vector<std::string_view> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices,
const std::string_view arg)
std::vector<std::string> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices,
const std::string_view arg)
{
std::vector<std::string_view> args;
std::vector<std::string> args;
for (i32 i = 0; i < argc; ++i)
{
if (!consumer.can_consume())
@ -882,7 +985,7 @@ namespace blt::argparse
"' is not a valid choice for argument '", arg,
"'! 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))
{