postionals may work now?

v2
Brett 2025-02-19 20:52:55 -05:00
parent 5f9ea32671
commit 188e9dba88
3 changed files with 165 additions and 56 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake)
set(BLT_VERSION 4.0.17)
set(BLT_VERSION 4.0.18)
set(BLT_TARGET BLT)

View File

@ -33,6 +33,7 @@
#include <functional>
#include <type_traits>
#include <blt/iterator/enumerate.h>
#include <blt/std/expected.h>
#include <blt/std/ranges.h>
#include <blt/std/utility.h>
@ -455,12 +456,17 @@ namespace blt::argparse
set_default(true);
break;
case action_t::STORE_CONST:
case action_t::APPEND_CONST:
set_nargs(0);
break;
case action_t::COUNT:
set_nargs(0);
as_type<size_t>();
break;
case action_t::HELP:
case action_t::VERSION:
set_nargs(0);
break;
default:
break;
}
@ -643,8 +649,11 @@ namespace blt::argparse
void parse_flag(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,
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,
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,
std::string_view arg);
std::optional<std::string> m_name;
std::optional<std::string> m_usage;

View File

@ -434,58 +434,18 @@ namespace blt::argparse
make_string("Error expected at least one argument to be consumed by '", arg, '\''));
[[fallthrough]];
case nargs_t::ALL:
std::vector<std::string_view> args;
while (consumer.can_consume() && !consumer.peek().is_flag())
args.emplace_back(consumer.consume().get_argument());
flag->m_dest_vec_func(dest, parsed_args, args);
auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr);
if (!result)
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
"' is not a valid choice for argument '", arg,
"'! Expected one of ", result.error()));
flag->m_dest_vec_func(dest, parsed_args, result.value());
break;
}
},
[&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc)
{
std::vector<std::string_view> args;
for (i32 i = 0; i < argc; ++i)
{
if (!consumer.can_consume())
{
throw detail::missing_argument_error(
make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i));
}
if (consumer.peek().is_flag())
{
std::cout << "Warning: arg '" << arg << "' expects " << argc <<
" arguments to be consumed but we found a flag '" << consumer.peek().
get_argument() << "'. We will comply as this may be desired if this argument is a file." << std::endl;
}
args.push_back(consumer.consume().get_argument());
}
if (flag->m_choices)
{
auto& choices = *flag->m_choices;
for (const auto str : args)
{
if (!choices.contains(str))
{
std::string valid_choices = "{";
for (const auto& [i, choice] : enumerate(choices))
{
valid_choices += choice;
if (i != choices.size() - 1)
valid_choices += ", ";
}
valid_choices += "}";
throw detail::bad_choice_error(make_string('\'', str, "' is not a valid choice for argument '", arg,
"'! Expected one of ", valid_choices));
}
}
}
if (args.size() != static_cast<size_t>(argc))
{
throw std::runtime_error(
"This error condition should not be possible. "
"Args consumed didn't equal the arguments requested and previous checks didn't fail. "
"Please report as an issue on the GitHub");
}
const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg);
switch (flag->m_action)
{
@ -514,7 +474,8 @@ namespace blt::argparse
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, '\''));
make_string("Append const chosen as an action but const value not provided for argument '", arg,
'\''));
}
if (parsed_args.contains(dest))
{
@ -522,8 +483,9 @@ namespace blt::argparse
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));
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)
{
@ -594,7 +556,8 @@ namespace blt::argparse
return primitive + static_cast<type>(1);
}
else
throw detail::type_error("Error: count called but stored type is " + blt::type_string<type>());
throw detail::type_error(
"Error: count called but stored type is " + blt::type_string<type>());
},
[](auto&) -> detail::arg_data_t
{
@ -609,10 +572,10 @@ namespace blt::argparse
break;
case action_t::HELP:
print_help();
std::exit(1);
std::exit(0);
case action_t::VERSION:
print_version();
break;
std::exit(0);
}
}
}, flag->m_nargs);
@ -620,6 +583,75 @@ namespace blt::argparse
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;
const auto dest = positional->m_dest.value_or(std::string{arg});
std::visit(lambda_visitor{
[&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum)
{
switch (arg_enum)
{
case nargs_t::IF_POSSIBLE:
throw detail::bad_positional(
"Positional argument asked to consume if possible. We do not consider this to be a valid ask.");
case nargs_t::ALL_AT_LEAST_ONE:
if (!consumer.can_consume())
throw detail::missing_argument_error(
make_string("Error expected at least one argument to be consumed by '", arg, '\''));
[[fallthrough]];
case nargs_t::ALL:
auto result = consume_until_flag_or_end(
consumer, positional->m_choices ? &*positional->m_choices : nullptr);
if (!result)
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
"' is not a valid choice for argument '", arg,
"'! Expected one of ", result.error()));
positional->m_dest_vec_func(dest, parsed_args, result.value());
break;
}
},
[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);
switch (positional->m_action)
{
case action_t::STORE:
if (argc == 0)
throw detail::missing_argument_error(
make_string("Argument '", arg, "'s action is store but takes in no arguments?"));
if (argc == 1)
positional->m_dest_func(dest, parsed_args, args.front());
else
throw detail::unexpected_argument_error(make_string("Argument '", arg,
"'s action is store but takes in more than one argument. "
"Did you mean to use action_t::APPEND or action_t::EXTEND?"));
break;
case action_t::APPEND:
case action_t::EXTEND:
if (argc == 0)
throw detail::missing_argument_error(
make_string("Argument '", arg, "'s action is append or extend but takes in no arguments."));
positional->m_dest_vec_func(dest, parsed_args, args);
break;
case action_t::APPEND_CONST:
throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments");
case action_t::STORE_CONST:
throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments");
case action_t::STORE_TRUE:
throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments");
case action_t::STORE_FALSE:
throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments");
case action_t::COUNT:
throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments");
case action_t::HELP:
print_help();
std::exit(0);
case action_t::VERSION:
print_version();
std::exit(0);
}
}
}, positional->m_nargs);
}
void argument_parser_t::handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
@ -640,6 +672,74 @@ 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)
{
std::vector<std::string_view> args;
while (consumer.can_consume() && !consumer.peek().is_flag())
{
if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
{
std::string valid_choices = "{";
for (const auto& [i, choice] : enumerate(*allowed_choices))
{
valid_choices += choice;
if (i != allowed_choices->size() - 1)
valid_choices += ", ";
}
valid_choices += "}";
return unexpected(valid_choices);
}
args.emplace_back(consumer.consume().get_argument());
}
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_view> args;
for (i32 i = 0; i < argc; ++i)
{
if (!consumer.can_consume())
{
throw detail::missing_argument_error(
make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i));
}
if (consumer.peek().is_flag())
{
std::cout << "Warning: arg '" << arg << "' expects " << argc <<
" arguments to be consumed but we found a flag '" << consumer.peek().
get_argument() <<
"'. We will comply as this may be desired if this argument is a file." << std::endl;
}
if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
{
std::string valid_choices = "{";
for (const auto& [i, choice] : enumerate(*allowed_choices))
{
valid_choices += choice;
if (i != allowed_choices->size() - 1)
valid_choices += ", ";
}
valid_choices += "}";
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
"' is not a valid choice for argument '", arg,
"'! Expected one of ", valid_choices));
}
args.push_back(consumer.consume().get_argument());
}
if (args.size() != static_cast<size_t>(argc))
{
throw std::runtime_error(
"This error condition should not be possible. "
"Args consumed didn't equal the arguments requested and previous checks didn't fail. "
"Please report as an issue on the GitHub");
}
return args;
}
std::pair<argument_string_t, argument_storage_t> argument_subparser_t::parse(argument_consumer_t& consumer)
{
if (!consumer.can_consume())