minor work
parent
e8e891d4fc
commit
f403e8a69b
|
@ -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.20)
|
set(BLT_VERSION 4.0.21)
|
||||||
|
|
||||||
set(BLT_TARGET BLT)
|
set(BLT_TARGET BLT)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <blt/iterator/iterator.h>
|
||||||
#include <blt/std/assert.h>
|
#include <blt/std/assert.h>
|
||||||
#include <blt/std/expected.h>
|
#include <blt/std/expected.h>
|
||||||
#include <blt/std/ranges.h>
|
#include <blt/std/ranges.h>
|
||||||
|
@ -45,6 +46,7 @@ namespace blt::argparse
|
||||||
class argument_subparser_t;
|
class argument_subparser_t;
|
||||||
class argument_builder_t;
|
class argument_builder_t;
|
||||||
class argument_storage_t;
|
class argument_storage_t;
|
||||||
|
class argument_positional_storage_t;
|
||||||
|
|
||||||
enum class action_t
|
enum class action_t
|
||||||
{
|
{
|
||||||
|
@ -532,7 +534,7 @@ namespace blt::argparse
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
argument_builder_t& set_dest(const std::string& dest)
|
argument_builder_t& set_dest(const std::string_view& dest)
|
||||||
{
|
{
|
||||||
m_dest = dest;
|
m_dest = dest;
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -554,6 +556,42 @@ namespace blt::argparse
|
||||||
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_view>& values)> m_dest_vec_func;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class argument_positional_storage_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
argument_positional_storage_t() = default;
|
||||||
|
|
||||||
|
argument_builder_t& add(const std::string_view name)
|
||||||
|
{
|
||||||
|
positional_arguments.emplace_back(name, argument_builder_t{});
|
||||||
|
return positional_arguments.back().second;
|
||||||
|
}
|
||||||
|
|
||||||
|
argument_builder_t& peek()
|
||||||
|
{
|
||||||
|
return positional_arguments[current_positional].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
argument_builder_t& next()
|
||||||
|
{
|
||||||
|
return positional_arguments[current_positional++].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool has_positional() const
|
||||||
|
{
|
||||||
|
return current_positional < positional_arguments.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto remaining() const
|
||||||
|
{
|
||||||
|
return iterate(positional_arguments).skip(current_positional);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::pair<std::string, argument_builder_t>> positional_arguments;
|
||||||
|
size_t current_positional = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class argument_parser_t
|
class argument_parser_t
|
||||||
{
|
{
|
||||||
friend argument_subparser_t;
|
friend argument_subparser_t;
|
||||||
|
@ -580,9 +618,11 @@ namespace blt::argparse
|
||||||
|
|
||||||
argument_builder_t& add_positional(const std::string_view arg)
|
argument_builder_t& add_positional(const std::string_view arg)
|
||||||
{
|
{
|
||||||
m_argument_builders.emplace_back(std::make_unique<argument_builder_t>());
|
auto& b = m_positional_arguments.add(arg);
|
||||||
m_positional_arguments.emplace(arg, m_argument_builders.back().get());
|
b.set_dest(std::string{arg});
|
||||||
return *m_argument_builders.back();
|
b.set_required(true);
|
||||||
|
b.set_nargs(1);
|
||||||
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
argument_subparser_t& add_subparser(std::string_view dest);
|
argument_subparser_t& add_subparser(std::string_view dest);
|
||||||
|
@ -688,7 +728,7 @@ namespace blt::argparse
|
||||||
std::vector<std::pair<std::string_view, argument_subparser_t>> m_subparsers;
|
std::vector<std::pair<std::string_view, argument_subparser_t>> m_subparsers;
|
||||||
std::vector<std::unique_ptr<argument_builder_t>> m_argument_builders;
|
std::vector<std::unique_ptr<argument_builder_t>> m_argument_builders;
|
||||||
hashmap_t<std::string_view, argument_builder_t*> m_flag_arguments;
|
hashmap_t<std::string_view, argument_builder_t*> m_flag_arguments;
|
||||||
hashmap_t<std::string_view, argument_builder_t*> m_positional_arguments;
|
argument_positional_storage_t m_positional_arguments;
|
||||||
hashset_t<char> allowed_flag_prefixes = {'-', '+', '/'};
|
hashset_t<char> allowed_flag_prefixes = {'-', '+', '/'};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,6 @@ namespace blt::argparse
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
std::cerr << e.what() << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,7 +317,7 @@ namespace blt::argparse
|
||||||
|
|
||||||
// Valid case: No arguments
|
// Valid case: No arguments
|
||||||
const std::vector<std::string_view> valid_args = {"./program"};
|
const std::vector<std::string_view> valid_args = {"./program"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args, 0) && "nargs=0: Should accept no arguments");
|
BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail");
|
||||||
|
|
||||||
// Invalid case: 1 argument
|
// Invalid case: 1 argument
|
||||||
const std::vector<std::string_view> invalid_args = {"./program", "arg1"};
|
const std::vector<std::string_view> invalid_args = {"./program", "arg1"};
|
||||||
|
@ -354,7 +353,7 @@ namespace blt::argparse
|
||||||
|
|
||||||
// Valid case: 2 arguments
|
// Valid case: 2 arguments
|
||||||
const std::vector<std::string_view> valid_args = {"./program", "arg1", "arg2"};
|
const std::vector<std::string_view> valid_args = {"./program", "arg1", "arg2"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args, 2) && "nargs=2: Should accept exactly 2 arguments");
|
BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store");
|
||||||
|
|
||||||
// Invalid case: 0 arguments
|
// Invalid case: 0 arguments
|
||||||
const std::vector<std::string_view> invalid_args_0 = {"./program"};
|
const std::vector<std::string_view> invalid_args_0 = {"./program"};
|
||||||
|
@ -371,45 +370,47 @@ namespace blt::argparse
|
||||||
std::cout << "Success: test_nargs_2\n";
|
std::cout << "Success: test_nargs_2\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_nargs_all() {
|
void test_nargs_all()
|
||||||
|
{
|
||||||
std::cout << "[Running Test: test_nargs_all]\n";
|
std::cout << "[Running Test: test_nargs_all]\n";
|
||||||
|
|
||||||
// Valid case: No arguments
|
// Valid case: No arguments
|
||||||
const std::vector<std::string_view> valid_args_0 = {"./program"};
|
const std::vector<std::string_view> valid_args_0 = {"./program"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args_0, argparse::nargs_t::ALL),
|
BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) &&
|
||||||
"nargs=ALL: Should accept all remaining arguments (even if none left)");
|
"nargs=ALL: No arguments present. Should fail.)");
|
||||||
|
|
||||||
// Valid case: Multiple arguments
|
// Valid case: Multiple arguments
|
||||||
const std::vector<std::string_view> valid_args_2 = {"./program", "arg1", "arg2"};
|
const std::vector<std::string_view> valid_args_2 = {"./program", "arg1", "arg2"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL),
|
BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) &&
|
||||||
"nargs=ALL: Should accept all remaining arguments");
|
"nargs=ALL: Should accept all remaining arguments");
|
||||||
|
|
||||||
// Valid case: Many arguments
|
// Valid case: Many arguments
|
||||||
const std::vector<std::string_view> valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"};
|
const std::vector<std::string_view> valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL),
|
BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) &&
|
||||||
"nargs=ALL: Should accept all remaining arguments");
|
"nargs=ALL: Should accept all remaining arguments");
|
||||||
|
|
||||||
std::cout << "Success: test_nargs_all\n";
|
std::cout << "Success: test_nargs_all\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test case for nargs_t::ALL_AT_LEAST_ONE
|
// Test case for nargs_t::ALL_AT_LEAST_ONE
|
||||||
void test_nargs_all_at_least_one() {
|
void test_nargs_all_at_least_one()
|
||||||
|
{
|
||||||
std::cout << "[Running Test: test_nargs_all_at_least_one]\n";
|
std::cout << "[Running Test: test_nargs_all_at_least_one]\n";
|
||||||
|
|
||||||
// Valid case: 1 argument
|
// Valid case: 1 argument
|
||||||
const std::vector<std::string_view> valid_args_1 = {"arg1"};
|
const std::vector<std::string_view> valid_args_1 = {"./program", "arg1"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE),
|
BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
|
||||||
"nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it");
|
"nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it");
|
||||||
|
|
||||||
// Valid case: Multiple arguments
|
// Valid case: Multiple arguments
|
||||||
const std::vector<std::string_view> valid_args_3 = {"arg1", "arg2", "arg3"};
|
const std::vector<std::string_view> valid_args_3 = {"./program", "arg1", "arg2", "arg3"};
|
||||||
BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE),
|
BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
|
||||||
"nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments");
|
"nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments");
|
||||||
|
|
||||||
// Invalid case: No arguments
|
// Invalid case: No arguments
|
||||||
const std::vector<std::string_view> invalid_args_0 = {};
|
const std::vector<std::string_view> invalid_args_0 = {"./program"};
|
||||||
BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE),
|
BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
|
||||||
"nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided");
|
"nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided");
|
||||||
|
|
||||||
std::cout << "Success: test_nargs_all_at_least_one\n";
|
std::cout << "Success: test_nargs_all_at_least_one\n";
|
||||||
}
|
}
|
||||||
|
@ -479,7 +480,6 @@ namespace blt::argparse
|
||||||
if (!m_name)
|
if (!m_name)
|
||||||
m_name = consumer.absolute_first().get_argument();
|
m_name = consumer.absolute_first().get_argument();
|
||||||
hashset_t<std::string> found_flags;
|
hashset_t<std::string> found_flags;
|
||||||
hashset_t<std::string> found_positional;
|
|
||||||
argument_storage_t parsed_args;
|
argument_storage_t parsed_args;
|
||||||
// first, we consume flags which may be part of this parser
|
// first, we consume flags which may be part of this parser
|
||||||
while (consumer.can_consume() && consumer.peek().is_flag())
|
while (consumer.can_consume() && consumer.peek().is_flag())
|
||||||
|
@ -494,20 +494,31 @@ namespace blt::argparse
|
||||||
|
|
||||||
while (consumer.can_consume())
|
while (consumer.can_consume())
|
||||||
{
|
{
|
||||||
const auto key = consumer.consume();
|
if (consumer.peek().is_flag())
|
||||||
if (key.is_flag())
|
handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume());
|
||||||
handle_compound_flags(found_flags, parsed_args, consumer, key);
|
|
||||||
else
|
else
|
||||||
{
|
parse_positional(parsed_args, consumer, consumer.peek().get_argument());
|
||||||
const auto pos = m_positional_arguments.find(key.get_argument());
|
|
||||||
if (pos == m_positional_arguments.end())
|
|
||||||
throw detail::bad_positional(make_string("Error: Unknown positional argument: ", key.get_argument()));
|
|
||||||
found_positional.insert(std::string{key.get_argument()});
|
|
||||||
parse_positional(parsed_args, consumer, key.get_argument());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag");
|
handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag");
|
||||||
handle_missing_and_default_args(m_positional_arguments, found_positional, parsed_args, "positional");
|
|
||||||
|
for (auto& [name, value] : m_positional_arguments.remaining())
|
||||||
|
{
|
||||||
|
std::visit(lambda_visitor{
|
||||||
|
[](const nargs_t)
|
||||||
|
{
|
||||||
|
},
|
||||||
|
[](const int argc)
|
||||||
|
{
|
||||||
|
if (argc == 0)
|
||||||
|
throw detail::bad_positional("Positional Argument takes no values, this is invalid!");
|
||||||
|
}
|
||||||
|
}, value.m_nargs);
|
||||||
|
|
||||||
|
if (value.m_required)
|
||||||
|
throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program"));
|
||||||
|
if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name)))
|
||||||
|
parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value);
|
||||||
|
}
|
||||||
|
|
||||||
return parsed_args;
|
return parsed_args;
|
||||||
}
|
}
|
||||||
|
@ -724,8 +735,10 @@ namespace blt::argparse
|
||||||
|
|
||||||
void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg)
|
void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg)
|
||||||
{
|
{
|
||||||
auto positional = m_positional_arguments.find(arg)->second;
|
if (!m_positional_arguments.has_positional())
|
||||||
const auto dest = positional->m_dest.value_or(std::string{arg});
|
throw detail::missing_argument_error(make_string("Error: No positional arguments were defined for this parser!"));
|
||||||
|
auto& positional = m_positional_arguments.next();
|
||||||
|
const auto dest = positional.m_dest.value_or(std::string{arg});
|
||||||
std::visit(lambda_visitor{
|
std::visit(lambda_visitor{
|
||||||
[&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum)
|
[&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum)
|
||||||
{
|
{
|
||||||
|
@ -741,27 +754,27 @@ namespace blt::argparse
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case nargs_t::ALL:
|
case nargs_t::ALL:
|
||||||
auto result = consume_until_flag_or_end(
|
auto result = consume_until_flag_or_end(
|
||||||
consumer, positional->m_choices ? &*positional->m_choices : nullptr);
|
consumer, positional.m_choices ? &*positional.m_choices : nullptr);
|
||||||
if (!result)
|
if (!result)
|
||||||
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
|
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
|
||||||
"' is not a valid choice for argument '", arg,
|
"' is not a valid choice for argument '", arg,
|
||||||
"'! Expected one of ", result.error()));
|
"'! Expected one of ", result.error()));
|
||||||
positional->m_dest_vec_func(dest, parsed_args, result.value());
|
positional.m_dest_vec_func(dest, parsed_args, result.value());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc)
|
[this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc)
|
||||||
{
|
{
|
||||||
const auto args = consume_argc(argc, consumer, positional->m_choices ? &*positional->m_choices : nullptr, arg);
|
const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg);
|
||||||
|
|
||||||
switch (positional->m_action)
|
switch (positional.m_action)
|
||||||
{
|
{
|
||||||
case action_t::STORE:
|
case action_t::STORE:
|
||||||
if (argc == 0)
|
if (argc == 0)
|
||||||
throw detail::missing_argument_error(
|
throw detail::missing_argument_error(
|
||||||
make_string("Argument '", arg, "'s action is store but takes in no arguments?"));
|
make_string("Argument '", arg, "'s action is store but takes in no arguments?"));
|
||||||
if (argc == 1)
|
if (argc == 1)
|
||||||
positional->m_dest_func(dest, parsed_args, args.front());
|
positional.m_dest_func(dest, parsed_args, args.front());
|
||||||
else
|
else
|
||||||
throw detail::unexpected_argument_error(make_string("Argument '", arg,
|
throw detail::unexpected_argument_error(make_string("Argument '", arg,
|
||||||
"'s action is store but takes in more than one argument. "
|
"'s action is store but takes in more than one argument. "
|
||||||
|
@ -772,7 +785,7 @@ namespace blt::argparse
|
||||||
if (argc == 0)
|
if (argc == 0)
|
||||||
throw detail::missing_argument_error(
|
throw detail::missing_argument_error(
|
||||||
make_string("Argument '", arg, "'s action is append or extend but takes in no arguments."));
|
make_string("Argument '", arg, "'s action is append or extend but takes in no arguments."));
|
||||||
positional->m_dest_vec_func(dest, parsed_args, args);
|
positional.m_dest_vec_func(dest, parsed_args, args);
|
||||||
break;
|
break;
|
||||||
case action_t::APPEND_CONST:
|
case action_t::APPEND_CONST:
|
||||||
throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments");
|
throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments");
|
||||||
|
@ -792,7 +805,7 @@ namespace blt::argparse
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, positional->m_nargs);
|
}, positional.m_nargs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void argument_parser_t::handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
|
void argument_parser_t::handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
|
||||||
|
|
Loading…
Reference in New Issue