2025-02-12 02:54:22 -05:00
|
|
|
/*
|
|
|
|
* <Short Description>
|
|
|
|
* Copyright (C) 2025 Brett Terpstra
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2025-02-13 17:47:27 -05:00
|
|
|
#include <iostream>
|
2025-02-12 02:54:22 -05:00
|
|
|
#include <blt/parse/argparse_v2.h>
|
|
|
|
#include <blt/std/assert.h>
|
2025-02-17 21:43:09 -05:00
|
|
|
#include <blt/meta/type_traits.h>
|
2025-02-12 02:54:22 -05:00
|
|
|
|
|
|
|
namespace blt::argparse
|
|
|
|
{
|
|
|
|
namespace detail
|
|
|
|
{
|
|
|
|
// Unit Tests for class argument_string_t
|
|
|
|
// Test Case 1: Ensure the constructor handles flags correctly
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_flag_basic(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("-f", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
|
2025-02-12 15:43:54 -05:00
|
|
|
BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 2: Ensure the constructor handles long flags correctly
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_long_flag(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("--file", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
|
2025-02-12 15:43:54 -05:00
|
|
|
BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 3: Ensure positional arguments are correctly identified
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_positional_argument(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("filename.txt", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional.");
|
|
|
|
BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 5: Handle an empty string
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_empty_input(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag.");
|
|
|
|
BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_single_hyphen(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("-", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag.");
|
2025-02-12 15:43:54 -05:00
|
|
|
BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value.");
|
|
|
|
BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 8: Handle arguments with prefix only (like "--")
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_double_hyphen(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("--", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag.");
|
2025-02-12 15:43:54 -05:00
|
|
|
BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value.");
|
|
|
|
BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 9: Validate edge case of an argument with spaces
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_with_spaces(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg(" ", prefixes);
|
2025-02-12 19:42:50 -05:00
|
|
|
BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags.");
|
|
|
|
BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 10: Validate arguments with numeric characters
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_numeric_flag(const hashset_t<char>& prefixes)
|
2025-02-12 02:54:22 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("-123", prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags.");
|
2025-02-12 15:43:54 -05:00
|
|
|
BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string.");
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
|
|
|
|
2025-02-12 19:42:50 -05:00
|
|
|
// Test Case 11: Ensure the constructor handles '+' flag correctly
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_plus_flag_basic(const hashset_t<char>& prefixes)
|
2025-02-12 19:42:50 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("+f", prefixes);
|
2025-02-12 19:42:50 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
|
|
|
|
BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_single_plus(const hashset_t<char>& prefixes)
|
2025-02-12 19:42:50 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("+", prefixes);
|
2025-02-12 19:42:50 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag.");
|
|
|
|
BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value.");
|
|
|
|
BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Case 14: Handle arguments with prefix only (like '++')
|
2025-02-13 01:53:21 -05:00
|
|
|
void test_argument_string_t_double_plus(const hashset_t<char>& prefixes)
|
2025-02-12 19:42:50 -05:00
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const argument_string_t arg("++", prefixes);
|
2025-02-12 19:42:50 -05:00
|
|
|
BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag.");
|
|
|
|
BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value.");
|
|
|
|
BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string.");
|
|
|
|
}
|
|
|
|
|
2025-02-13 17:47:27 -05:00
|
|
|
|
2025-02-12 02:54:22 -05:00
|
|
|
void run_all_tests_argument_string_t()
|
|
|
|
{
|
2025-02-13 01:53:21 -05:00
|
|
|
const hashset_t<char> prefixes = {'-', '+'};
|
|
|
|
test_argument_string_t_flag_basic(prefixes);
|
|
|
|
test_argument_string_t_long_flag(prefixes);
|
|
|
|
test_argument_string_t_positional_argument(prefixes);
|
|
|
|
test_argument_string_t_empty_input(prefixes);
|
|
|
|
test_argument_string_t_single_hyphen(prefixes);
|
|
|
|
test_argument_string_t_double_hyphen(prefixes);
|
|
|
|
test_argument_string_t_with_spaces(prefixes);
|
|
|
|
test_argument_string_t_numeric_flag(prefixes);
|
|
|
|
test_argument_string_t_plus_flag_basic(prefixes);
|
|
|
|
test_argument_string_t_single_plus(prefixes);
|
|
|
|
test_argument_string_t_double_plus(prefixes);
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
2025-02-12 19:42:50 -05:00
|
|
|
|
2025-02-12 02:54:22 -05:00
|
|
|
void test()
|
|
|
|
{
|
|
|
|
run_all_tests_argument_string_t();
|
|
|
|
}
|
2025-02-16 23:22:00 -05:00
|
|
|
|
|
|
|
[[nodiscard]] std::string subparse_error::error_string() const
|
|
|
|
{
|
|
|
|
std::string message = "Subparser Error: ";
|
|
|
|
message += m_found_string;
|
|
|
|
message += " is not a valid command. Allowed commands are: {";
|
|
|
|
for (const auto [i, allowed_string] : enumerate(m_allowed_strings))
|
|
|
|
{
|
|
|
|
if (allowed_string.size() > 1)
|
|
|
|
message += '[';
|
|
|
|
for (const auto [j, alias] : enumerate(allowed_string))
|
|
|
|
{
|
|
|
|
message += alias;
|
|
|
|
if (j < alias.size() - 2)
|
|
|
|
message += ", ";
|
|
|
|
else if (j < alias.size())
|
|
|
|
message += ", or ";
|
|
|
|
}
|
|
|
|
if (allowed_string.size() > 1)
|
|
|
|
message += ']';
|
|
|
|
if (i != m_allowed_strings.size() - 1)
|
|
|
|
message += ' ';
|
|
|
|
}
|
|
|
|
message += "}";
|
|
|
|
return message;
|
|
|
|
}
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|
2025-02-13 17:47:27 -05:00
|
|
|
|
|
|
|
argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest)
|
|
|
|
{
|
|
|
|
m_subparsers.emplace_back(dest, argument_subparser_t{*this});
|
|
|
|
return m_subparsers.back().second;
|
|
|
|
}
|
|
|
|
|
2025-02-16 23:22:00 -05:00
|
|
|
argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer)
|
2025-02-13 17:47:27 -05:00
|
|
|
{
|
2025-02-16 23:22:00 -05:00
|
|
|
hashset_t<std::string_view> found_flags;
|
|
|
|
hashset_t<std::string_view> found_positional;
|
|
|
|
argument_storage_t parsed_args;
|
|
|
|
// first, we consume flags which may be part of this parser
|
|
|
|
while (consumer.can_consume() && consumer.peek().is_flag())
|
|
|
|
{
|
|
|
|
const auto key = consumer.consume();
|
|
|
|
const auto flag = m_flag_arguments.find(key.get_argument());
|
|
|
|
if (flag == m_flag_arguments.end())
|
|
|
|
{
|
|
|
|
std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
found_flags.insert(key.get_argument());
|
|
|
|
parse_flag(parsed_args, consumer, key.get_argument());
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
for (auto& [key, subparser] : m_subparsers)
|
|
|
|
{
|
|
|
|
auto [parsed_subparser, storage] = subparser.parse(consumer);
|
|
|
|
storage.m_data.insert({key, detail::arg_data_t{parsed_subparser.get_argument()}});
|
|
|
|
parsed_args.add(storage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const detail::missing_argument_error& e)
|
|
|
|
{
|
|
|
|
std::cerr << "Error: " << e.what() << std::endl;
|
|
|
|
print_usage();
|
|
|
|
exit(1);
|
|
|
|
} catch (const detail::subparse_error& e)
|
2025-02-13 17:47:27 -05:00
|
|
|
{
|
2025-02-16 23:22:00 -05:00
|
|
|
std::cerr << e.error_string() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
while (consumer.can_consume())
|
|
|
|
{
|
|
|
|
const auto key = consumer.consume();
|
|
|
|
if (key.is_flag())
|
2025-02-13 17:47:27 -05:00
|
|
|
{
|
2025-02-16 23:22:00 -05:00
|
|
|
const auto flag = m_flag_arguments.find(key.get_argument());
|
|
|
|
if (flag == m_flag_arguments.end())
|
|
|
|
{
|
|
|
|
std::cerr << "Error: Unknown flag: " << key.get_argument() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
found_flags.insert(key.get_argument());
|
|
|
|
parse_flag(parsed_args, consumer, key.get_argument());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const auto pos = m_positional_arguments.find(key.get_argument());
|
|
|
|
if (pos == m_positional_arguments.end())
|
|
|
|
{
|
|
|
|
std::cerr << "Error: Unknown positional argument: " << key.get_argument() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
found_positional.insert(key.get_argument());
|
|
|
|
parse_positional(parsed_args, consumer, key.get_argument());
|
2025-02-13 17:47:27 -05:00
|
|
|
}
|
|
|
|
}
|
2025-02-16 23:22:00 -05:00
|
|
|
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");
|
|
|
|
|
|
|
|
return parsed_args;
|
2025-02-13 17:47:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void argument_parser_t::print_help()
|
|
|
|
{
|
2025-02-16 23:22:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void argument_parser_t::print_usage()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2025-02-17 01:47:42 -05:00
|
|
|
void argument_parser_t::print_version()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2025-02-16 23:22:00 -05:00
|
|
|
void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg)
|
|
|
|
{
|
|
|
|
auto& flag = m_flag_arguments[arg];
|
2025-02-17 02:20:40 -05:00
|
|
|
auto dest = flag->m_dest.value_or(std::string{arg});
|
2025-02-16 23:22:00 -05:00
|
|
|
std::visit(lambda_visitor{
|
|
|
|
[&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum)
|
|
|
|
{
|
|
|
|
switch (arg_enum)
|
|
|
|
{
|
|
|
|
case nargs_t::IF_POSSIBLE:
|
|
|
|
if (consumer.can_consume() && !consumer.peek().is_flag())
|
|
|
|
flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (flag->m_const_value)
|
|
|
|
parsed_args.m_data.insert({dest, *flag->m_const_value});
|
|
|
|
}
|
|
|
|
break;
|
2025-02-17 02:20:40 -05:00
|
|
|
case nargs_t::ALL_AT_LEAST_ONE:
|
2025-02-16 23:22:00 -05:00
|
|
|
if (!consumer.can_consume())
|
2025-02-17 01:47:42 -05:00
|
|
|
std::cerr << "Error expected at least one argument to be consumed by '" << arg << '\'' << std::endl;
|
2025-02-17 02:20:40 -05:00
|
|
|
[[fallthrough]];
|
2025-02-16 23:22:00 -05:00
|
|
|
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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
2025-02-17 01:47:42 -05:00
|
|
|
[&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc)
|
2025-02-16 23:22:00 -05:00
|
|
|
{
|
2025-02-17 01:47:42 -05:00
|
|
|
std::vector<std::string_view> args;
|
|
|
|
for (i32 i = 0; i < argc; ++i)
|
|
|
|
{
|
|
|
|
if (!consumer.can_consume())
|
|
|
|
{
|
|
|
|
std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found " << i <<
|
|
|
|
std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
if (consumer.peek().is_flag())
|
|
|
|
{
|
|
|
|
std::cerr << "Error expected " << argc << " arguments to be consumed by '" << arg << "' but found a flag '" <<
|
|
|
|
consumer.peek().get_argument() << "' instead!" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
args.push_back(consumer.consume().get_argument());
|
|
|
|
}
|
2025-02-17 02:20:40 -05:00
|
|
|
if (args.size() != static_cast<size_t>(argc))
|
2025-02-17 01:47:42 -05:00
|
|
|
{
|
|
|
|
std::cerr <<
|
|
|
|
"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"
|
|
|
|
<< std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
2025-02-18 00:46:30 -05:00
|
|
|
|
2025-02-18 01:32:26 -05:00
|
|
|
switch (flag->m_action)
|
2025-02-17 01:47:42 -05:00
|
|
|
{
|
2025-02-18 01:32:26 -05:00
|
|
|
case action_t::STORE:
|
|
|
|
if (argc == 0)
|
2025-02-17 01:47:42 -05:00
|
|
|
{
|
2025-02-18 01:32:26 -05:00
|
|
|
std::cerr << "Error: argument '" << arg <<
|
|
|
|
"' action is store but takes in no arguments. This condition is invalid!" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
if (argc == 1)
|
2025-02-18 00:46:30 -05:00
|
|
|
flag->m_dest_func(dest, parsed_args, args.front());
|
2025-02-18 01:32:26 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
std::cerr << "Error: argument '" << arg << "' action is store but takes in more than one argument. " <<
|
|
|
|
"This condition is invalid, did you mean to use action_t::APPEND or action_t::EXTEND?" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case action_t::APPEND:
|
|
|
|
case action_t::EXTEND:
|
|
|
|
if (argc == 0)
|
|
|
|
{
|
|
|
|
std::cerr << "Error: argument '" << arg <<
|
|
|
|
"' action is append or extend but takes in no arguments. This condition is invalid!" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
flag->m_dest_vec_func(dest, parsed_args, args);
|
|
|
|
break;
|
|
|
|
case action_t::APPEND_CONST:
|
|
|
|
if (argc != 0)
|
|
|
|
{
|
|
|
|
std::cerr << "Error: argument '" << arg << "' action is append const but takes in arguments. "
|
|
|
|
"This condition is invalid!" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
if (flag->m_const_value)
|
|
|
|
{
|
|
|
|
std::cerr << "Append const chosen as an action but const value not provided for argument '" << arg << '\'' <<
|
|
|
|
std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
std::cerr << "Invalid type for argument '" << arg << "' expected list type, found '"
|
|
|
|
<< blt::type_string<decltype(primitive)>() << "' with value " << primitive << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
},
|
|
|
|
[&flag, arg](auto& vec)
|
|
|
|
{
|
|
|
|
using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
|
|
|
|
if (!std::holds_alternative<type>(*flag->m_const_value))
|
2025-02-17 21:43:09 -05:00
|
|
|
{
|
2025-02-18 01:32:26 -05:00
|
|
|
std::cerr << "Constant value for argument '" << arg <<
|
|
|
|
"' type doesn't match values already present! Expected to be of type '" <<
|
|
|
|
blt::type_string<type>() << "'!" << std::endl;
|
2025-02-17 21:43:09 -05:00
|
|
|
std::exit(1);
|
2025-02-18 01:32:26 -05:00
|
|
|
}
|
|
|
|
vec.push_back(std::get<type>(*flag->m_const_value));
|
|
|
|
});
|
|
|
|
std::visit(visitor, data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto visitor = detail::arg_meta_type_helper_t::make_visitor(
|
|
|
|
[&flag, &parsed_args, &dest](auto& primitive)
|
|
|
|
{
|
|
|
|
std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
|
|
|
|
vec.push_back(primitive);
|
|
|
|
parsed_args.m_data.insert({dest, std::move(vec)});
|
|
|
|
},
|
|
|
|
[](auto&)
|
|
|
|
{
|
|
|
|
std::cerr << "Append const should not be a list type!" << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
});
|
|
|
|
std::visit(visitor, *flag->m_const_value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case action_t::STORE_CONST:
|
|
|
|
if (argc != 0)
|
|
|
|
{
|
2025-02-17 01:47:42 -05:00
|
|
|
std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl;
|
|
|
|
print_usage();
|
|
|
|
std::exit(1);
|
2025-02-18 01:32:26 -05:00
|
|
|
}
|
|
|
|
if (!flag->m_const_value)
|
|
|
|
{
|
|
|
|
std::cerr << "Store const flag called with no value. This condition doesn't make sense." << std::endl;
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
parsed_args.m_data.insert({dest, *flag->m_const_value});
|
|
|
|
case action_t::STORE_TRUE:
|
|
|
|
if (argc != 0)
|
|
|
|
{
|
2025-02-17 01:47:42 -05:00
|
|
|
std::cerr << "Store true flag called with an argument. This condition doesn't make sense." << std::endl;
|
|
|
|
print_usage();
|
|
|
|
std::exit(1);
|
2025-02-18 01:32:26 -05:00
|
|
|
}
|
|
|
|
parsed_args.m_data.insert({dest, true});
|
|
|
|
case action_t::STORE_FALSE:
|
|
|
|
if (argc != 0)
|
|
|
|
{
|
2025-02-17 01:47:42 -05:00
|
|
|
std::cerr << "Store false flag called with an argument. This condition doesn't make sense." << std::endl;
|
|
|
|
print_usage();
|
|
|
|
std::exit(1);
|
2025-02-18 01:32:26 -05:00
|
|
|
}
|
|
|
|
parsed_args.m_data.insert({dest, false});
|
|
|
|
case action_t::COUNT:
|
|
|
|
if (parsed_args.m_data.contains(dest))
|
|
|
|
{
|
|
|
|
auto visitor = detail::arg_meta_type_helper_t::make_visitor(
|
|
|
|
[&args](auto& primitive) -> detail::arg_data_t
|
|
|
|
{
|
|
|
|
using type = meta::remove_cvref_t<decltype(primitive)>;
|
|
|
|
if constexpr (std::is_convertible_v<decltype(args.size()), type>)
|
2025-02-17 21:43:09 -05:00
|
|
|
{
|
2025-02-18 01:32:26 -05:00
|
|
|
return primitive + static_cast<type>(args.size());
|
|
|
|
}
|
|
|
|
else
|
2025-02-17 21:43:09 -05:00
|
|
|
{
|
2025-02-18 01:32:26 -05:00
|
|
|
std::cerr << "Error: count called but stored type is " << blt::type_string<type>() << std::endl;
|
2025-02-17 21:43:09 -05:00
|
|
|
std::exit(1);
|
|
|
|
}
|
2025-02-18 01:32:26 -05:00
|
|
|
},
|
|
|
|
[](auto&) -> detail::arg_data_t
|
|
|
|
{
|
|
|
|
std::cerr <<
|
|
|
|
"List present on count. This condition doesn't make any sense! (How did we get here, please report this!)";
|
|
|
|
std::exit(1);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]);
|
2025-02-17 01:47:42 -05:00
|
|
|
}
|
2025-02-18 01:32:26 -05:00
|
|
|
else
|
|
|
|
parsed_args.m_data.insert({dest, args.size()});
|
|
|
|
break;
|
|
|
|
case action_t::HELP:
|
|
|
|
print_help();
|
|
|
|
std::exit(1);
|
|
|
|
case action_t::VERSION:
|
|
|
|
print_version();
|
|
|
|
break;
|
2025-02-17 01:47:42 -05:00
|
|
|
}
|
2025-02-16 23:22:00 -05:00
|
|
|
}
|
|
|
|
}, flag->m_nargs);
|
|
|
|
}
|
|
|
|
|
|
|
|
void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg)
|
|
|
|
{
|
|
|
|
}
|
2025-02-13 17:47:27 -05:00
|
|
|
|
2025-02-16 23:22:00 -05:00
|
|
|
void argument_parser_t::handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
|
|
|
|
const hashset_t<std::string_view>& found, argument_storage_t& parsed_args,
|
|
|
|
const std::string_view type)
|
|
|
|
{
|
|
|
|
for (const auto& [key, value] : arguments)
|
|
|
|
{
|
|
|
|
if (!found.contains(key))
|
|
|
|
{
|
|
|
|
if (value->m_required)
|
|
|
|
{
|
|
|
|
std::cerr << "Error: " << type << " argument '" << key << "' was not found but is required by the program" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
2025-02-17 02:20:40 -05:00
|
|
|
auto dest = value->m_dest.value_or(std::string{key});
|
2025-02-16 23:22:00 -05:00
|
|
|
if (value->m_default_value && !parsed_args.contains(dest))
|
|
|
|
parsed_args.m_data.insert({dest, *value->m_default_value});
|
|
|
|
}
|
|
|
|
}
|
2025-02-13 17:47:27 -05:00
|
|
|
}
|
|
|
|
|
2025-02-16 23:22:00 -05:00
|
|
|
std::pair<argument_string_t, argument_storage_t> argument_subparser_t::parse(argument_consumer_t& consumer)
|
2025-02-13 17:47:27 -05:00
|
|
|
{
|
2025-02-16 23:22:00 -05:00
|
|
|
if (!consumer.can_consume())
|
2025-02-13 17:47:27 -05:00
|
|
|
throw detail::missing_argument_error("Subparser requires an argument.");
|
|
|
|
const auto key = consumer.consume();
|
|
|
|
if (key.is_flag())
|
|
|
|
throw detail::subparse_error(key.get_argument(), get_allowed_strings());
|
|
|
|
const auto it = m_parsers.find(key.get_name());
|
2025-02-16 23:22:00 -05:00
|
|
|
argument_parser_t* parser;
|
2025-02-13 17:47:27 -05:00
|
|
|
if (it == m_parsers.end())
|
|
|
|
{
|
|
|
|
const auto it2 = m_aliases.find(key.get_name());
|
|
|
|
if (it2 == m_aliases.end())
|
|
|
|
throw detail::subparse_error(key.get_argument(), get_allowed_strings());
|
2025-02-16 23:22:00 -05:00
|
|
|
parser = it2->second;
|
2025-02-13 17:47:27 -05:00
|
|
|
}
|
2025-02-16 23:22:00 -05:00
|
|
|
else
|
|
|
|
parser = &it->second;
|
|
|
|
parser->m_name = m_parent->m_name;
|
|
|
|
return {key, parser->parse(consumer)};
|
2025-02-13 17:47:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::vector<std::string_view>> argument_subparser_t::get_allowed_strings() const
|
|
|
|
{
|
|
|
|
std::vector<std::vector<std::string_view>> vec;
|
|
|
|
for (const auto& [key, value] : m_parsers)
|
|
|
|
{
|
|
|
|
std::vector<std::string_view> aliases;
|
|
|
|
aliases.push_back(key);
|
|
|
|
for (const auto& [alias, parser] : m_aliases)
|
|
|
|
{
|
|
|
|
if (parser == &value)
|
|
|
|
aliases.push_back(alias);
|
|
|
|
}
|
|
|
|
vec.emplace_back(std::move(aliases));
|
|
|
|
}
|
|
|
|
return vec;
|
|
|
|
}
|
2025-02-12 02:54:22 -05:00
|
|
|
}
|