From 30f975e165a91b3e3662f317ab77df590628b596 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 21 Feb 2025 16:37:05 -0500 Subject: [PATCH] more tests, i think most things work now? --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 29 +++-- src/blt/parse/argparse_v2.cpp | 189 ++++++++++++++++++++++++-------- 3 files changed, 166 insertions(+), 54 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 977ef33..864ca1d 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.21) +set(BLT_VERSION 4.0.22) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index ee1988d..a4cc3cf 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -188,7 +188,7 @@ namespace blt::argparse } }; - using arg_meta_type_helper_t = arg_data_helper_t; + using arg_meta_type_helper_t = arg_data_helper_t; using arg_data_t = arg_meta_type_helper_t::variant_t; template @@ -376,9 +376,9 @@ namespace blt::argparse return std::get(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(m_data.at(key)); + return std::get(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& values) + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& 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::convert(value)); }; - m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& 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(); 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 m_dest_func; // dest, storage, value input - std::function& values)> m_dest_vec_func; + std::function& 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()); + 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& arguments, const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); - static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, + static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, hashset_t* allowed_choices); - static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, std::string_view arg); std::optional m_name; @@ -746,7 +755,7 @@ namespace blt::argparse 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_parsers.emplace(name, argument_parser_t{}); ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); return m_parsers[name]; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 3c81ab8..84077d7 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -67,7 +67,8 @@ namespace blt::argparse template auto ensure_is_string(T&& t) { - if constexpr (std::is_arithmetic_v>) + if constexpr (std::is_arithmetic_v> && !(std::is_same_v + || std::is_same_v || std::is_same_v)) return std::to_string(std::forward(t)); else return std::forward(t); @@ -82,6 +83,12 @@ namespace blt::argparse return out; } + template + std::vector make_arguments(Strings... strings) + { + return std::vector{"./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(); - parser.add_flag("/y").as_type(); + parser.add_flag("/y").as_type(); const std::vector 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(); + 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(); + + const auto a1 = make_arguments("-a", "--required", "hello"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r1.get("--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("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r2.get("--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>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); + BLT_ASSERT(r3.get("-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>("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>("wow") == std::vector{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("--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(), "' with value ", primitive)); - }, - [&flag, arg](auto& vec) - { - using type = typename meta::remove_cvref_t::value_type; - if (!std::holds_alternative(*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(), "'!")); - } - vec.push_back(std::get(*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(), "' with value ", primitive)); + }, + [&flag, arg](auto& vec) + { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*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(), "'!")); + } + vec.push_back(std::get(*flag->m_const_value)); + }), data); } else { - auto visitor = detail::arg_meta_type_helper_t::make_visitor( - [&parsed_args, &dest](auto& primitive) - { - std::vector> 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> 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::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, - hashset_t* allowed_choices) + expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices) { - std::vector args; + std::vector 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 argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, - hashset_t* allowed_choices, - const std::string_view arg) + std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, + hashset_t* allowed_choices, + const std::string_view arg) { - std::vector args; + std::vector 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(argc)) {