fix argparse help

main
Brett 2025-03-12 23:26:44 -04:00
parent 04a5c01704
commit 4fab2595bc
3 changed files with 1531 additions and 1597 deletions

View File

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

@ -1 +1 @@
Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734
Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3

View File

@ -28,13 +28,11 @@
namespace blt::argparse
{
constexpr static auto printer_primitive = [](const auto& v)
{
constexpr static auto printer_primitive = [](const auto& v) {
std::cout << v;
};
constexpr static auto printer_vector = [](const auto& v)
{
constexpr static auto printer_vector = [](const auto& v) {
std::cout << "[";
for (const auto& [i, a] : enumerate(v))
{
@ -53,16 +51,13 @@ namespace blt::argparse
if constexpr (std::is_convertible_v<T, const char*>)
{
return std::char_traits<char>::length(t);
}
else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>)
} else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>)
{
return 1;
}
else if constexpr (std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string>)
} else if constexpr (std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string>)
{
return t.size();
}
else
} else
{
return 0;
}
@ -74,14 +69,12 @@ namespace blt::argparse
if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>)
{
return std::string(t);
}
else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>)
} else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>)
{
std::string str;
str += t;
return str;
}
else
} else
{
return t;
}
@ -105,11 +98,12 @@ namespace blt::argparse
class aligned_internal_string_t
{
public:
explicit aligned_internal_string_t(std::string& str, const size_t max_line_length,
const size_t line_start_size): string(str), max_line_size(max_line_length),
line_start_size(line_start_size)
{
}
explicit aligned_internal_string_t(std::string& str, const size_t max_line_length, const size_t line_start_size): string(str),
max_line_size(
max_line_length),
line_start_size(
line_start_size)
{}
void add(const std::string_view str) const
{
@ -149,8 +143,7 @@ namespace blt::argparse
{
size_t j = i;
for (; j < str.size() && !std::isblank(str[j]); ++j)
{
}
{}
add(std::string_view(str.data() + i, j - i));
if (j < str.size())
add(std::string_view(str.data() + j, 1));
@ -173,10 +166,9 @@ namespace blt::argparse
class aligner_t
{
public:
aligner_t(std::vector<std::string>& buffer, const size_t start_index, const size_t max_line_size):
buffer(buffer), start_index(start_index), max_line_size(max_line_size)
{
}
aligner_t(std::vector<std::string>& buffer, const size_t start_index, const size_t max_line_size): buffer(buffer), start_index(start_index),
max_line_size(max_line_size)
{}
void align(const size_t spaces_between) const
{
@ -186,8 +178,7 @@ namespace blt::argparse
{
auto size = static_cast<i64>(v.size());
for (; size > 0 && std::isblank(v[size - 1]); size--)
{
}
{}
aligned_size = std::max(aligned_size, static_cast<size_t>(size));
}
const auto offset_size = aligned_size + spaces_between;
@ -201,16 +192,14 @@ namespace blt::argparse
[[nodiscard]] auto iter()
{
return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x)
{
return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) {
return aligned_internal_string_t{x, max_line_size, x.size()};
});
}
[[nodiscard]] auto iter() const
{
return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x)
{
return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) {
return aligned_internal_string_t{x, max_line_size, x.size()};
});
}
@ -375,11 +364,8 @@ namespace blt::argparse
for (auto& [name, value] : positional_storage.remaining())
{
std::visit(lambda_visitor{
[](const nargs_t)
{
},
[](const int argc)
{
[](const nargs_t) {},
[](const int argc) {
if (argc == 0)
throw detail::bad_positional("Positional Argument takes no values, this is invalid!");
}
@ -434,6 +420,7 @@ namespace blt::argparse
{
help += "Positional Arguments:";
help.newline();
auto mark = help.mark();
for (auto& [name, builder] : m_positional_arguments)
{
help += '\t';
@ -442,49 +429,55 @@ namespace blt::argparse
help += name;
if (!builder.m_required)
help += ']';
help.newline();
}
mark.align(4);
for (auto zipped_positionals : mark.iter().zip(m_positional_arguments))
{
auto& line = std::get<0>(zipped_positionals);
auto& [name, builder] = std::get<1>(zipped_positionals);
line += builder.m_help.value_or("");
if (builder.m_default_value && !(builder.m_action == action_t::STORE_TRUE || builder.m_action == action_t::STORE_FALSE))
{
if (!std::isblank(help.str().back()))
help += " ";
help += "(Default: ";
if (!std::isblank(line.str().back()))
line += " ";
line += "(Default: ";
std::visit(detail::arg_meta_type_helper_t::make_visitor(
[&](auto& value)
{
help += value;
line += value;
},
[&](auto& vec)
{
if constexpr (!std::is_same_v<std::decay_t<meta::remove_cvref_t<decltype(vec)>>, std::vector<bool>>)
{
help += '[';
line += '[';
for (const auto& [i, v] : enumerate(vec))
{
help += v;
line += v;
if (i != vec.size() - 1)
help += ", ";
line += ", ";
}
help += ']';
line += ']';
}
}), *builder.m_default_value);
help += ")";
line += ")";
}
if (builder.m_choices)
{
if (!std::isblank(help.str().back()))
help += " ";
help += "(Choices: ";
if (!std::isblank(line.str().back()))
line += " ";
line += "(Choices: ";
for (const auto& [i, v] : enumerate(*builder.m_choices))
{
help += '\'';
help += v;
help += '\'';
line += '\'';
line += v;
line += '\'';
if (i != builder.m_choices->size() - 1)
help += ", ";
line += ", ";
}
help += ')';
line += ')';
}
help.newline();
help.newline();
}
}
@ -508,14 +501,12 @@ namespace blt::argparse
}
const argument_string_t arg{flag_list.front(), allowed_flag_prefixes};
auto metavar = builder->m_metavar.value_or(string::toUpperCase(arg.get_name()));
auto lambda = [&]()
{
auto lambda = [&]() {
help += ' ';
help += metavar;
};
std::visit(lambda_visitor{
[&](const nargs_t type)
{
[&](const nargs_t type) {
lambda();
switch (type)
{
@ -527,8 +518,7 @@ namespace blt::argparse
break;
}
},
[&](const int argc)
{
[&](const int argc) {
if (argc == 0)
return;
lambda();
@ -552,13 +542,9 @@ namespace blt::argparse
if (!std::isblank(str.str().back()))
str += " ";
str += "(Default: '";
std::visit(detail::arg_meta_type_helper_t::make_visitor(
[&](auto& value)
{
std::visit(detail::arg_meta_type_helper_t::make_visitor([&](auto& value) {
str += value;
},
[&](auto& vec)
{
}, [&](auto& vec) {
if constexpr (!std::is_same_v<std::decay_t<meta::remove_cvref_t<decltype(vec)>>, std::vector<bool>>)
{
str += '[';
@ -639,8 +625,7 @@ namespace blt::argparse
singleFlags[arg.get_flag()].emplace_back(arg.get_name());
else
compoundFlags.emplace_back(arg, value);
}
else
} else
compoundFlags.emplace_back(arg, value);
}
@ -661,14 +646,12 @@ namespace blt::argparse
const auto& builder = kv.second;
aligner += builder->m_required ? '<' : '[';
aligner += name.get_argument();
auto lambda = [&]()
{
auto lambda = [&]() {
aligner += ' ';
aligner += builder->m_metavar.value_or(string::toUpperCase(name.get_name()));
};
std::visit(lambda_visitor{
[&](const nargs_t type)
{
[&](const nargs_t type) {
lambda();
switch (type)
{
@ -680,8 +663,7 @@ namespace blt::argparse
break;
}
},
[&](const int argc)
{
[&](const int argc) {
for (int j = 0; j < argc; j++)
lambda();
}
@ -710,8 +692,8 @@ namespace blt::argparse
std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl;
}
void argument_parser_t::handle_compound_flags(hashset_t<std::string>& found_flags, argument_storage_t& parsed_args,
argument_consumer_t& consumer, const argument_string_t& arg)
void argument_parser_t::handle_compound_flags(hashset_t<std::string>& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer,
const argument_string_t& arg)
{
// i kinda hate this, TODO?
std::vector<std::string> compound_flags;
@ -719,8 +701,7 @@ namespace blt::argparse
{
for (const auto c : arg.get_name())
compound_flags.emplace_back(std::string{arg.get_flag()} + c);
}
else
} else
{
if (arg.get_flag().size() > 2)
throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!"));
@ -742,8 +723,7 @@ namespace blt::argparse
auto flag = m_flag_arguments.find(arg)->second;
const auto dest = flag->m_dest.value_or(std::string{arg});
std::visit(lambda_visitor{
[&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum)
{
[&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) {
switch (arg_enum)
{
case nargs_t::IF_POSSIBLE:
@ -770,8 +750,7 @@ namespace blt::argparse
break;
}
},
[&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc)
{
[&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) {
const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg);
switch (flag->m_action)
@ -800,22 +779,16 @@ namespace blt::argparse
make_string("Argument '", arg, "'s action is append const but takes in arguments."));
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,
'\''));
throw detail::missing_value_error(make_string(
"Append const chosen as an action but const value not provided for argument '", arg, '\''));
}
if (parsed_args.contains(dest))
{
auto& data = parsed_args.m_data[dest];
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 '",
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<decltype(primitive)>(), "' with value ", primitive));
},
[&flag, arg](auto& vec)
{
}, [&flag, arg](auto& vec) {
using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
if (!std::holds_alternative<type>(*flag->m_const_value))
{
@ -825,18 +798,13 @@ namespace blt::argparse
}
vec.push_back(std::get<type>(*flag->m_const_value));
}), data);
}
else
{
std::visit(detail::arg_meta_type_helper_t::make_visitor(
[&parsed_args, &dest](auto& primitive)
} else
{
std::visit(detail::arg_meta_type_helper_t::make_visitor([&parsed_args, &dest](auto& primitive) {
std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
vec.emplace_back(primitive);
parsed_args.m_data.emplace(dest, std::move(vec));
},
[](auto&)
{
}, [](auto&) {
throw detail::type_error("Append const should not be a list type!");
}), *flag->m_const_value);
}
@ -872,27 +840,20 @@ namespace blt::argparse
case action_t::COUNT:
if (parsed_args.m_data.contains(dest))
{
auto visitor = detail::arg_meta_type_helper_t::make_visitor(
[](auto& primitive) -> detail::arg_data_t
{
auto visitor = detail::arg_meta_type_helper_t::make_visitor([](auto& primitive) -> detail::arg_data_t {
using type = meta::remove_cvref_t<decltype(primitive)>;
if constexpr (std::is_convertible_v<decltype(1), type>)
{
return primitive + static_cast<type>(1);
}
else
} else
throw detail::type_error("Error: count called but stored type is " + blt::type_string<type>());
}, [](auto&) -> detail::arg_data_t {
throw detail::type_error(
"Error: count called but stored type is " + blt::type_string<type>());
},
[](auto&) -> detail::arg_data_t
{
throw detail::type_error("List present on count. This condition doesn't make any sense! "
"List present on count. This condition doesn't make any sense! "
"(How did we get here, please report this!)");
}
);
});
parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]);
}
else // I also hate this!
} else // I also hate this!
flag->m_dest_func(dest, parsed_args, "1");
break;
case action_t::HELP:
@ -914,8 +875,7 @@ namespace blt::argparse
auto& positional = storage.next();
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)
{
[&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) {
switch (arg_enum)
{
case nargs_t::IF_POSSIBLE:
@ -927,8 +887,7 @@ namespace blt::argparse
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);
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,
@ -937,8 +896,7 @@ namespace blt::argparse
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);
switch (positional.m_action)
@ -1023,8 +981,7 @@ namespace blt::argparse
return args;
}
std::vector<std::string> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices,
std::vector<std::string> 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> args;
@ -1032,15 +989,13 @@ namespace blt::argparse
{
if (!consumer.can_consume())
{
throw detail::missing_argument_error(
make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i));
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;
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()))
{
@ -1052,8 +1007,7 @@ namespace blt::argparse
valid_choices += ", ";
}
valid_choices += "}";
throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
"' is not a valid choice for argument '", arg,
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.emplace_back(consumer.consume().get_argument());
@ -1061,8 +1015,7 @@ namespace blt::argparse
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. "
"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;
@ -1194,7 +1147,6 @@ namespace blt::argparse
BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string.");
}
void run_all_tests_argument_string_t()
{
const hashset_t<char> prefixes = {'-', '+'};
@ -1247,8 +1199,7 @@ namespace blt::argparse
{
parser.parse(args);
BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'");
}
catch (...)
} catch (...)
{
BLT_ASSERT(true && "Correctly threw on bad flag prefix");
}
@ -1278,8 +1229,7 @@ namespace blt::argparse
{
parser.parse(args);
BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'");
}
catch (...)
} catch (...)
{
BLT_ASSERT(true && "Correctly threw an exception for invalid flag");
}
@ -1320,8 +1270,7 @@ namespace blt::argparse
{
auto parsed_args = parser.parse(consumer);
return consumer.remaining() == 0;
}
catch (const std::exception&)
} catch (const std::exception&)
{
return false;
}
@ -1393,18 +1342,15 @@ namespace blt::argparse
// Valid case: No arguments
const std::vector<std::string_view> valid_args_0 = {"./program"};
BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) &&
"nargs=ALL: No arguments present. Should fail.)");
BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && "nargs=ALL: No arguments present. Should fail.)");
// Valid case: Multiple arguments
const std::vector<std::string_view> valid_args_2 = {"./program", "arg1", "arg2"};
BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) &&
"nargs=ALL: Should accept all remaining arguments");
BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments");
// Valid case: Many arguments
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) &&
"nargs=ALL: Should accept all remaining arguments");
BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments");
std::cout << "Success: test_nargs_all\n";
}
@ -1497,20 +1443,16 @@ namespace blt::argparse
{
parser.parse(a2);
BLT_ASSERT(false && "Parsing should fail due to invalid flag '--hello'");
}
catch (...)
{
}
} catch (...)
{}
const auto a3 = make_arguments("--hello", "crazy", "not_a_choice");
try
{
parser.parse(a3);
BLT_ASSERT(false && "Parsing should fail due to invalid positional 'iam'");
}
catch (...)
{
}
} catch (...)
{}
std::cout << "Success: run_choice_test\n";
}
@ -1541,20 +1483,16 @@ namespace blt::argparse
{
parser.parse(a1);
BLT_ASSERT(false && "Subparser should throw an error when positional not supplied");
}
catch (...)
{
}
} 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 (...)
{
}
} catch (...)
{}
const auto a3 = make_arguments("n1", "--silly", "path_n1");
const auto r3 = parser.parse(a3);
@ -1568,10 +1506,8 @@ namespace blt::argparse
{
parser.parse(a4);
BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied");
}
catch (...)
{
}
} catch (...)
{}
const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2");
const auto r5 = parser.parse(a5);
@ -1586,10 +1522,8 @@ namespace blt::argparse
{
parser.parse(a6);
BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser");
}
catch (const std::exception&)
{
}
} catch (const std::exception&)
{}
const auto a7 = make_arguments("n3");
const auto r7 = parser.parse(a7);