Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework parsing positionals #63

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
161 changes: 102 additions & 59 deletions include/argparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,15 @@ namespace argparse
};

private:
using tokens = std::vector<std::string>;
struct Token
{
std::string m_token;
bool m_consumed = false;

bool operator==(Token const &) const = default;
};

using tokens = std::vector<Token>;
using optstring = std::optional<std::string>;

class HelpRequested {};
Expand Down Expand Up @@ -340,6 +348,38 @@ namespace argparse
return result;
}

static auto join(tokens const & tokens, std::string const & separator) -> std::string
{
auto result = std::string();

for (auto it = tokens.begin(); it != tokens.end(); ++it)
{
if (it != tokens.begin())
{
result += separator;
}
result += it->m_token;
}

return result;
}

static auto join(std::ranges::view auto strings, std::string const & separator) -> std::string
{
auto result = std::string();

for (auto it = strings.begin(); it != strings.end(); ++it)
{
if (it != strings.begin())
{
result += separator;
}
result += *it;
}

return result;
}

auto parse_optional_arguments(tokens args) -> tokens
{
for (auto const & arg : m_arguments
Expand All @@ -364,16 +404,17 @@ namespace argparse

static auto remove_pseudo_arguments(tokens args) -> tokens
{
std::erase(args, "--");
std::erase(args, Token{"--"});

return args;
}

auto ensure_no_unrecognised_arguments(tokens const & args) const -> void
{
if (!args.empty())
auto unconsumed = std::ranges::filter_view(args, [](auto const & token) { return !token.m_consumed; });
if (!unconsumed.empty())
{
throw parsing_error(std::format("unrecognised arguments: {}", join(args, " ")));
throw parsing_error(std::format("unrecognised arguments: {}", join(unconsumed | std::views::transform([](auto const & token) { return token.m_token; }), " ")));
}
}

Expand Down Expand Up @@ -646,22 +687,23 @@ namespace argparse

auto parse_args(tokens args) -> tokens override
{
auto consumable = std::ranges::drop_while_view(args, [](auto const & token) { return token.m_consumed; });
if (has_nargs())
{
if (has_nargs_number())
{
parse_arguments_number(args, std::min(get_nargs_number(), args.size()));
parse_arguments(consumable | std::views::take(get_nargs_number()));
}
else
{
parse_arguments_option(args);
parse_arguments_option(consumable);
}
}
else
{
if (!args.empty())
if (!consumable.empty())
{
consume_arg(args, m_value);
m_value = consume_arg(consumable.front());
}
}

Expand Down Expand Up @@ -710,23 +752,22 @@ namespace argparse
}

private:
auto parse_arguments_number(tokens & args, std::size_t number) -> void
auto parse_arguments(std::ranges::view auto args) -> void
{
auto values = std::vector<std::any>(number);
consume_args(args, values);
auto values = consume_args(args);

m_value = m_options.type_handler->transform(values);
}

auto parse_arguments_option(tokens & args) -> void
auto parse_arguments_option(std::ranges::view auto args) -> void
{
switch (get_nargs_option())
{
case zero_or_one:
{
if (!args.empty())
{
consume_arg(args, m_value);
m_value = consume_arg(args.front());
}
else
{
Expand All @@ -736,13 +777,12 @@ namespace argparse
}
case zero_or_more:
{
parse_arguments_number(args, args.size());
parse_arguments(args);
break;
}
case one_or_more:
{
auto values = std::vector<std::any>(args.size());
consume_args(args, values);
auto values = consume_args(args);
if (!values.empty())
{
m_value = m_options.type_handler->transform(values);
Expand All @@ -752,25 +792,25 @@ namespace argparse
}
}

auto consume_arg(tokens & args, std::any & value) const -> void
auto consume_arg(Token & arg) const -> std::any
{
if (!m_options.type_handler->from_string(args.front(), value))
std::any value;
if (!m_options.type_handler->from_string(arg.m_token, value))
{
throw parsing_error(std::format("argument {}: invalid value: '{}'", get_dest_name(), args.front()));
throw parsing_error(std::format("argument {}: invalid value: '{}'", get_dest_name(), arg.m_token));
}
if (!m_options.choices.empty())
{
check_choices(value);
}
args.erase(args.begin());
arg.m_consumed = true;
return value;
}

auto consume_args(tokens & args, std::vector<std::any> & values) const -> void
auto consume_args(std::ranges::view auto args) const -> std::vector<std::any>
{
for (auto & value : values)
{
consume_arg(args, value);
}
auto transformation = std::views::transform(args, [this](auto & arg) { return consume_arg(arg); }) | std::views::common;
return std::vector(transformation.begin(), transformation.end());
}

private:
Expand Down Expand Up @@ -810,11 +850,11 @@ namespace argparse
}
else
{
if (it == args.end() || it->starts_with("-"))
if (it == args.end() || it->m_token.starts_with("-"))
{
throw parsing_error(std::format("argument {}: expected one argument", join(get_names(), "/")));
}
consume_arg(args, it, m_value);
m_value = consume_arg(*it);
}
break;
case store_true:
Expand Down Expand Up @@ -918,7 +958,7 @@ namespace argparse
{
throw parsing_error(std::format("argument {}: expected {} argument{}", join(get_names(), "/"), std::to_string(nargs_number), nargs_number > 1 ? "s" : ""));
}
parse_arguments_number(args, it, nargs_number);
parse_arguments_number(it, nargs_number);
}

auto parse_arguments_option(tokens & args, tokens::iterator it) -> void
Expand All @@ -927,20 +967,20 @@ namespace argparse
{
case zero_or_one:
{
if (it == args.end() || it->starts_with("-"))
if (it == args.end() || it->m_token.starts_with("-"))
{
m_value = m_options.const_;
}
else
{
consume_arg(args, it, m_value);
m_value = consume_arg(*it);
}
break;
}
case zero_or_more:
{
auto const args_number = count_args(it, args.end());
parse_arguments_number(args, it, args_number);
parse_arguments_number(it, args_number);
break;
}
case one_or_more:
Expand All @@ -950,22 +990,21 @@ namespace argparse
{
throw parsing_error(std::format("argument {}: expected at least one argument", join(get_names(), "/")));
}
parse_arguments_number(args, it, args_number);
parse_arguments_number(it, args_number);
break;
}
}
}

auto parse_arguments_number(tokens & args, tokens::iterator it, std::size_t args_number) -> void
auto parse_arguments_number(tokens::iterator it, std::size_t args_number) -> void
{
auto values = std::vector<std::any>(args_number);
consume_args(args, it, values);
auto values = consume_args(it, args_number);
m_value = m_options.type_handler->transform(values);
}

auto find_pseudo_arg(tokens & args) const -> tokens::iterator
{
return std::ranges::find(args, "--");
return std::ranges::find(args, Token{"--"});
}

auto find_arg(tokens::iterator begin, tokens::iterator end) const -> std::pair<tokens::iterator, std::string>
Expand All @@ -976,15 +1015,15 @@ namespace argparse
{
if (name[1] != '-')
{
if (it->starts_with("-") && !it->starts_with("--") && it->find(name[1]) != std::string::npos)
if (it->m_token.starts_with("-") && !it->m_token.starts_with("--") && it->m_token.find(name[1]) != std::string::npos)
{
return {it, name};
}
}
else
{
auto const [first_it, second_it] = std::ranges::mismatch(name, *it);
if (first_it == name.end() && (second_it == it->end() || *second_it == '='))
auto const [first_it, second_it] = std::ranges::mismatch(name, it->m_token);
if (first_it == name.end() && (second_it == it->m_token.end() || *second_it == '='))
{
return {it, name};
}
Expand All @@ -997,12 +1036,12 @@ namespace argparse

auto consume_name(tokens & args, tokens::iterator it, std::string const & name) const -> tokens::iterator
{
if (auto const & arg = *it; arg.starts_with("--"))
if (auto const & arg = *it; arg.m_token.starts_with("--"))
{
if (auto const pos = arg.find('='); pos != std::string::npos)
if (auto const pos = arg.m_token.find('='); pos != std::string::npos)
{
auto const value = arg.substr(pos + 1);
*it = value;
auto const value = arg.m_token.substr(pos + 1);
*it = Token{value};
}
else
{
Expand All @@ -1011,22 +1050,22 @@ namespace argparse
}
else
{
if (it->size() != 2)
if (it->m_token.size() != 2)
{
auto const pos = it->find(name[1]);
it->erase(pos, 1);
auto const pos = it->m_token.find(name[1]);
it->m_token.erase(pos, 1);
if (m_options.action == store)
{
if (pos == 1)
{
it->erase(0, 1);
it->m_token.erase(0, 1);
}
else
{
auto const prefix = it->substr(0, pos);
auto const value = it->substr(pos);
*it = prefix;
it = args.insert(it, value);
auto const prefix = it->m_token.substr(0, pos);
auto const value = it->m_token.substr(pos);
*it = Token{prefix};
it = args.insert(it, Token{value});
}
}
}
Expand All @@ -1042,33 +1081,37 @@ namespace argparse
auto count_args(tokens::const_iterator it, tokens::const_iterator end) const -> std::size_t
{
auto result = std::size_t(0);
while (it != end && !it->starts_with('-'))
while (it != end && !it->m_token.starts_with('-'))
{
++result;
++it;
}
return result;
}

auto consume_arg(tokens & args, tokens::iterator & arg_it, std::any & value) const -> void
auto consume_arg(Token & arg) const -> std::any
{
if (!m_options.type_handler->from_string(*arg_it, value))
std::any value;
if (!m_options.type_handler->from_string(arg.m_token, value))
{
throw parsing_error(std::format("argument {}: invalid value: '{}'", join(get_names(), "/"), *arg_it));
throw parsing_error(std::format("argument {}: invalid value: '{}'", join(get_names(), "/"), arg.m_token));
}
if (!m_options.choices.empty())
{
check_choices(value);
}
arg_it = args.erase(arg_it);
arg.m_consumed = true;
return value;
}

auto consume_args(tokens & args, tokens::iterator & arg_it, std::vector<std::any> & values) const -> void
auto consume_args(tokens::iterator arg_it, std::size_t number) const -> std::vector<std::any>
{
for (auto & value : values)
std::vector<std::any> values;
for (std::size_t i = 0; i < number; ++i)
{
consume_arg(args, arg_it, value);
values.push_back(consume_arg(*(arg_it + i)));
}
return values;
}

auto get_name_for_dest() const -> std::string
Expand Down