From 389762e2aefb2f0843e51a9d48189c8cad6c850c Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Thu, 20 Feb 2025 00:47:27 -0500 Subject: [PATCH] positionals are broken because im silly --- CMakeLists.txt | 2 +- include/blt/parse/argparse_v2.h | 32 +++++++++- src/blt/parse/argparse_v2.cpp | 108 ++++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c902cf8..a9aa84a 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.18) +set(BLT_VERSION 4.0.19) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 9da2900..9d663f3 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -308,8 +308,16 @@ namespace blt::argparse class argument_consumer_t { public: - explicit argument_consumer_t(const span& args): m_begin(args.data()), m_end(args.data() + args.size()) + explicit argument_consumer_t(const span& args): m_absolute_begin(args.data()), m_begin(args.data() + 1), + m_end(args.data() + args.size()) { + BLT_ASSERT(!args.empty() && + "Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program"); + } + + [[nodiscard]] const argument_string_t& absolute_first() const + { + return *m_absolute_begin; } [[nodiscard]] argument_string_t peek(const i32 offset = 0) const @@ -348,6 +356,7 @@ namespace blt::argparse return m_end - m_begin; } + argument_string_t* m_absolute_begin; argument_string_t* m_begin; argument_string_t* m_end; }; @@ -580,9 +589,20 @@ namespace blt::argparse argument_storage_t parse(argument_consumer_t& consumer); // NOLINT + argument_storage_t parse(const std::vector& args) + { + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + argument_storage_t parse(const std::vector& args) { std::vector arg_strings; + arg_strings.reserve(args.size()); for (const auto& arg : args) arg_strings.emplace_back(arg, allowed_flag_prefixes); argument_consumer_t consumer{arg_strings}; @@ -592,6 +612,7 @@ namespace blt::argparse argument_storage_t parse(const int argc, const char** argv) { std::vector arg_strings; + arg_strings.reserve(argc); for (int i = 0; i < argc; ++i) arg_strings.emplace_back(argv[i], allowed_flag_prefixes); argument_consumer_t consumer{arg_strings}; @@ -643,6 +664,11 @@ namespace blt::argparse return m_epilogue; } + [[nodiscard]] const hashset_t& get_allowed_flag_prefixes() const + { + return allowed_flag_prefixes; + } + private: void handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, const argument_string_t& arg); @@ -653,7 +679,7 @@ namespace blt::argparse 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, - std::string_view arg); + std::string_view arg); std::optional m_name; std::optional m_usage; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index ba632f8..7fc9b91 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace blt::argparse { @@ -195,7 +196,7 @@ namespace blt::argparse void test_argparse_empty() { - std::vector const argv; + const std::vector argv{"./program"}; argument_parser_t parser; const auto args = parser.parse(argv); BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); @@ -208,7 +209,7 @@ namespace blt::argparse parser.add_flag("+b").set_action(action_t::STORE_FALSE); parser.add_flag("/c").as_type().set_action(action_t::STORE); - const std::vector args = {"-a", "+b", "/c", "42"}; + const std::vector args = {"./program", "-a", "+b", "/c", "42"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); @@ -224,7 +225,7 @@ namespace blt::argparse parser.add_flag("+b"); parser.add_flag("/c"); - const std::vector args = {"!d", "-a"}; + const std::vector args = {"./program", "!d", "-a"}; try { parser.parse(args); @@ -241,7 +242,7 @@ namespace blt::argparse argument_parser_t parser; parser.add_flag("-v").as_type().set_action(action_t::COUNT); - const std::vector args = {"-vvv"}; + const std::vector args = {"./program", "-vvv"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); @@ -255,7 +256,7 @@ namespace blt::argparse parser.add_flag("-x").as_type(); parser.add_flag("/y").as_type(); - const std::vector args = {"-x", "10", "!z", "/y", "value"}; + const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; try { parser.parse(args); @@ -277,7 +278,7 @@ namespace blt::argparse parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action - const std::vector args = {"-k", "100", "-t", "-f", "-c"}; + const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; const auto parsed_args = parser.parse(args); BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); @@ -286,6 +287,90 @@ namespace blt::argparse BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); } + // Helper function to simulate argument parsing for nargs tests + bool parse_arguments(const std::vector& args, int expected_nargs) + { + argument_parser_t parser; + + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); + argument_consumer_t consumer{arg_strings}; + + parser.add_positional("positional").set_nargs(expected_nargs); + try + { + auto parsed_args = parser.parse(consumer); + return consumer.remaining() == 0; + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + return false; + } + } + + // Test case for nargs = 0 + void test_nargs_0() + { + std::cout << "[Running Test: test_nargs_0]\n"; + + // Valid case: No arguments + const std::vector valid_args = {"./program"}; + BLT_ASSERT(parse_arguments(valid_args, 0) && "nargs=0: Should accept no arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); + + std::cout << "Success: test_nargs_0\n"; + } + + // Test case for nargs = 1 + void test_nargs_1() + { + std::cout << "[Running Test: test_nargs_1]\n"; + + // Valid case: 1 argument + const std::vector valid_args = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); + + // Invalid case: 2 arguments + const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); + + std::cout << "Success: test_nargs_1\n"; + } + + // Test case for nargs = 2 + void test_nargs_2() + { + std::cout << "[Running Test: test_nargs_2]\n"; + + // Valid case: 2 arguments + const std::vector valid_args = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args, 2) && "nargs=2: Should accept exactly 2 arguments"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); + + // Invalid case: 3 arguments + const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); + + std::cout << "Success: test_nargs_2\n"; + } + void run_argparse_flag_tests() { test_single_flag_prefixes(); @@ -295,11 +380,20 @@ namespace blt::argparse test_flags_with_different_actions(); } + void run_all_nargs_tests() + { + test_nargs_0(); + test_nargs_1(); + test_nargs_2(); + std::cout << "All nargs tests passed successfully.\n"; + } + void test() { run_all_tests_argument_string_t(); test_argparse_empty(); run_argparse_flag_tests(); + run_all_nargs_tests(); } [[nodiscard]] std::string subparse_error::error_string() const @@ -337,6 +431,8 @@ namespace blt::argparse argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { + if (!m_name) + m_name = consumer.absolute_first().get_argument(); hashset_t found_flags; hashset_t found_positional; argument_storage_t parsed_args;