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,7 +737,7 @@ 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(
@ -649,23 +754,21 @@ namespace blt::argparse
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))
{ {