Переглянути джерело

Provide did-you-mean for command-line arguments

default_compile_flags
vector-of-bool 3 роки тому
джерело
коміт
8d8853cb95
2 змінених файлів з 79 додано та 26 видалено
  1. +18
    -4
      src/dds.main.cpp
  2. +61
    -22
      src/debate/argument_parser.cpp

+ 18
- 4
src/dds.main.cpp Переглянути файл

@@ -9,6 +9,7 @@
#include <debate/enum.hpp>

#include <boost/leaf/handle_exception.hpp>
#include <fansi/styled.hpp>
#include <fmt/ostream.h>
#include <neo/event.hpp>

@@ -17,6 +18,8 @@
#include <iostream>
#include <locale>

using namespace fansi::literals;

static void load_locale() {
auto lang = dds::getenv("LANG");
if (!lang) {
@@ -54,12 +57,22 @@ int main_fn(std::string_view program_name, const std::vector<std::string>& argv)
},
[&](debate::unrecognized_argument,
debate::e_argument_parser p,
debate::e_arg_spelling arg) {
debate::e_arg_spelling arg,
debate::e_did_you_mean* dym) {
std::cerr << p.parser.usage_string(program_name) << '\n';
if (p.parser.subparsers()) {
fmt::print(std::cerr, "Unrecognized argument/subcommand: \"{}\"\n", arg.spelling);
fmt::print(std::cerr,
"Unrecognized argument/subcommand: \".bold.red[{}]\"\n"_styled,
arg.spelling);
} else {
fmt::print(std::cerr, "Unrecognized argument: \"{}\"\n", arg.spelling);
fmt::print(std::cerr,
"Unrecognized argument: \".bold.red[{}]\"\n"_styled,
arg.spelling);
}
if (dym) {
fmt::print(std::cerr,
" (Did you mean '.br.yellow[{}]'?)\n"_styled,
dym->candidate);
}
return 2;
},
@@ -108,7 +121,8 @@ int main_fn(std::string_view program_name, const std::vector<std::string>& argv)
return 2;
},
[&](debate::invalid_repitition, debate::e_argument_parser p, debate::e_arg_spelling sp) {
fmt::print(std::cerr << "{}\nArgument '{}' cannot be provided more than once\n",
fmt::print(std::cerr,
"{}\nArgument '{}' cannot be provided more than once\n",
p.parser.usage_string(program_name),
sp.spelling);
return 2;

+ 61
- 22
src/debate/argument_parser.cpp Переглянути файл

@@ -1,11 +1,15 @@
#include "./argument_parser.hpp"

/// XXX: Refactor this after pulling debate:: out of dds
#include <dds/dym.hpp>

#include <boost/leaf/error.hpp>
#include <boost/leaf/exception.hpp>
#include <boost/leaf/on_error.hpp>

#include <fmt/color.h>
#include <fmt/format.h>
#include <neo/scope.hpp>

#include <set>

@@ -32,7 +36,7 @@ struct parse_engine {
void see(const argument& arg) {
auto did_insert = seen.insert(&arg).second;
if (!did_insert && !arg.can_repeat) {
throw boost::leaf::exception(invalid_repitition("Invalid repitition"));
BOOST_LEAF_THROW_EXCEPTION(invalid_repitition("Invalid repitition"));
}
}

@@ -45,12 +49,48 @@ struct parse_engine {
finalize();
}

std::optional<std::string> find_nearest_arg_spelling(std::string_view given) const noexcept {
std::vector<std::string> candidates;
// Only match arguments of the corrent type
auto given_long = given.starts_with("--");
auto has_dash = given.starts_with("-");
auto parser = bottom_parser;
while (parser) {
for (auto& arg : parser->arguments()) {
if (given_long) {
for (auto& l : arg.long_spellings) {
candidates.push_back("--" + l);
}
}
if (has_dash) {
for (auto& s : arg.short_spellings) {
candidates.push_back("-" + s);
}
}
if (!has_dash && parser->subparsers()) {
auto&& grp = *parser->subparsers();
for (auto& p : grp._p_subparsers) {
candidates.push_back(p.name);
}
}
}
parser = parser->parent().pointer();
}
return dds::did_you_mean(given, candidates);
}

void parse_another() {
auto given = current_arg();
auto did_parse = try_parse_given(given);
if (!did_parse) {
throw boost::leaf::exception(unrecognized_argument("Unrecognized argument"),
e_arg_spelling{std::string(given)});
neo_defer {
auto dym = find_nearest_arg_spelling(given);
if (dym) {
boost::leaf::current_error().load(e_did_you_mean{*dym});
}
};
BOOST_LEAF_THROW_EXCEPTION(unrecognized_argument("Unrecognized argument"),
e_arg_spelling{std::string(given)});
}
}

@@ -81,7 +121,7 @@ struct parse_engine {

bool try_parse_long(strv tail, const strv given) {
if (tail == "help") {
throw boost::leaf::exception(help_request());
BOOST_LEAF_THROW_EXCEPTION(help_request());
}
auto argset = bottom_parser;
while (argset) {
@@ -115,8 +155,8 @@ struct parse_engine {
if (arg.nargs == 0) {
if (!tail.empty()) {
// We should not have a value
throw boost::leaf::exception(invalid_arguments("Argument does not expect a value"),
e_wrong_val_num{1});
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Argument does not expect a value"),
e_wrong_val_num{1});
}
// Just a switch. Dispatch
arg.action(given, given);
@@ -133,17 +173,17 @@ struct parse_engine {
tail.remove_prefix(1);
// The remainder is a single value
if (arg.nargs > 1) {
throw boost::leaf::exception(invalid_arguments("Invalid number of values"),
e_wrong_val_num{1});
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Invalid number of values"),
e_wrong_val_num{1});
}
arg.action(tail, given);
} else {
// Trailing words are arguments
for (auto i = 0; i < arg.nargs; ++i) {
if (at_end()) {
throw boost::leaf::exception(invalid_arguments(
"Invalid number of argument values"),
e_wrong_val_num{i});
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments(
"Invalid number of argument values"),
e_wrong_val_num{i});
}
arg.action(current_arg(), given);
shift();
@@ -164,7 +204,7 @@ struct parse_engine {

bool try_parse_short(strv tail, const strv given) {
if (tail == "h") {
throw boost::leaf::exception(help_request());
BOOST_LEAF_THROW_EXCEPTION(help_request());
}
auto argset = bottom_parser;
while (argset) {
@@ -213,7 +253,7 @@ struct parse_engine {
// The next argument is the value
shift();
if (at_end()) {
throw boost::leaf::exception(invalid_arguments("Expected a value"));
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Expected a value"));
}
arg.action(current_arg(), spelling);
shift();
@@ -228,16 +268,15 @@ struct parse_engine {
} else {
// Consume the next arguments
if (!tail.empty()) {
throw boost::leaf::exception(invalid_arguments(
"Wrong number of argument values given"),
e_wrong_val_num{1});
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments(
"Wrong number of argument values given"),
e_wrong_val_num{1});
}
shift();
for (auto i = 0; i < arg.nargs; ++i) {
if (at_end()) {
throw boost::leaf::exception(invalid_arguments(
"Wrong number of argument values"),
e_wrong_val_num{i});
BOOST_LEAF_THROW_EXCEPTION(invalid_arguments("Wrong number of argument values"),
e_wrong_val_num{i});
}
arg.action(current_arg(), spelling);
shift();
@@ -343,15 +382,15 @@ struct parse_engine {
argset = argset->parent().pointer();
}
if (bottom_parser->subparsers() && bottom_parser->subparsers()->required) {
throw boost::leaf::exception(missing_required("Expected a subcommand"));
BOOST_LEAF_THROW_EXCEPTION(missing_required("Expected a subcommand"));
}
}

void finalize(const argument_parser& argset) {
for (auto& arg : argset.arguments()) {
if (arg.required && !seen.contains(&arg)) {
throw boost::leaf::exception(missing_required("Required argument is missing"),
e_argument{arg});
BOOST_LEAF_THROW_EXCEPTION(missing_required("Required argument is missing"),
e_argument{arg});
}
}
}

Завантаження…
Відмінити
Зберегти