Support complex syntax for command line arguments

Support parameters prefixed by '-' or '--'.
Support parameters in form 'key=value'
This commit is contained in:
Robert Bendun 2023-02-20 18:16:20 +01:00
parent aaf6e6ec0c
commit 83ede4ef81
4 changed files with 89 additions and 56 deletions

View File

@ -13,7 +13,7 @@ VERSION := $(MAJOR).$(MINOR).$(PATCH)-dev+$(COMMIT)
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/ -Ilib/edit_distance.cc/
LDFLAGS=-flto
LDLIBS= -lpthread

View File

@ -50,7 +50,6 @@ static constexpr std::array all_parameters = [] {
std::cout << *maybe_docs << std::endl;
return;
}
std::cerr << "musique: error: cannot find documentation for given builtin" << std::endl;
std::exit(1);
};
@ -99,42 +98,89 @@ static constexpr std::array all_parameters = [] {
};
}();
bool cmd::accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args)
{
if (args.empty())
return false;
for (auto const& [name, handler] : all_parameters) {
// TODO Parameters starting with - or -- should be considered equal
if (name != args.front()) {
continue;
}
args = args.subspan(1);
std::visit(Overloaded {
[](Empty_Argument const& h) {
h();
},
[&args, name=name](Requires_Argument const& h) {
if (args.empty()) {
std::cerr << "musique: error: option " << std::quoted(name) << " requires an argument" << std::endl;
std::exit(1);
}
h(args.front());
args = args.subspan(1);
},
[&, name=name](Defines_Code const& h) {
if (args.empty()) {
std::cerr << "musique: error: option " << std::quoted(name) << " requires an argument" << std::endl;
std::exit(1);
}
runnables.push_back(h(args.front()));
args = args.subspan(1);
}
}, handler);
return true;
// Supported types of argument input:
// With arity = 0
// -i -j -k ≡ --i --j --k ≡ i j k
// With arity = 1
// -i 1 -j 2 ≡ --i 1 --j 2 ≡ i 1 j 2 ≡ --i=1 --j=2
// Arity ≥ 2 is not supported
std::optional<std::string_view> cmd::accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args)
{
if (args.empty()) {
return std::nullopt;
}
return false;
// This struct when function returns automatically ajust number of arguments used
struct Fix_Args_Array
{
std::string_view name() const { return m_name; }
std::optional<std::string_view> value() const
{
if (m_has_value) { m_success = m_value_consumed = true; return m_value; }
return std::nullopt;
}
void mark_success() { m_success = true; }
~Fix_Args_Array() { args = args.subspan(int(m_success) + (!m_packed * int(m_value_consumed))); }
std::span<char const*> &args;
std::string_view m_name = {};
std::string_view m_value = {};
bool m_has_value = false;
bool m_packed = false;
mutable bool m_success = false;
mutable bool m_value_consumed = false;
} state { .args = args };
std::string_view s = args.front();
if (s.starts_with("--")) s.remove_prefix(2);
if (s.starts_with('-')) s.remove_prefix(1);
state.m_name = s;
if (auto p = s.find('='); p != std::string_view::npos) {
state.m_name = s.substr(0, p);
state.m_value = s.substr(p+1);
state.m_has_value = true;
state.m_packed = true;
} else if (args.size() >= 2) {
state.m_has_value = true;
state.m_value = args[1];
}
for (auto const& [name, handler] : all_parameters) {
if (name != state.name()) {
continue;
}
std::visit(Overloaded {
[&state](Empty_Argument const& h) {
state.mark_success();
h();
},
[&state, name=name](Requires_Argument const& h) {
auto arg = state.value();
if (!arg) {
std::cerr << "musique: error: option " << std::quoted(name) << " requires an argument" << std::endl;
std::exit(1);
}
h(*arg);
},
[&state, &runnables, name=name](Defines_Code const& h) {
auto arg = state.value();
if (!arg) {
std::cerr << "musique: error: option " << std::quoted(name) << " requires an argument" << std::endl;
std::exit(1);
}
runnables.push_back(h(*arg));
}
}, handler);
return std::nullopt;
}
return state.name();
}

View File

@ -1,6 +1,7 @@
#ifndef MUSIQUE_CMD_HH
#define MUSIQUE_CMD_HH
#include <optional>
#include <span>
#include <string_view>
#include <variant>
@ -22,7 +23,7 @@ namespace cmd
};
/// Accept and execute next command line argument with its parameters if it has any
bool accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args);
std::optional<std::string_view> accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args);
/// Print all arguments that are similar to one provided
void print_close_matches(std::string_view arg);

View File

@ -321,35 +321,21 @@ static Result<bool> handle_repl_session_commands(std::string_view input, Runner
/// Fancy main that supports Result forwarding on error (Try macro)
static std::optional<Error> Main(std::span<char const*> args)
{
enable_repl = args.empty();
if (cmd::is_tty() && getenv("NO_COLOR") == nullptr) {
pretty::terminal_mode();
}
std::vector<cmd::Run> runnables;
for (;;) if (!cmd::accept_commandline_argument(runnables, args)) {
if (args.size()) {
std::cerr << "musique: error: Failed to recognize parameter " << std::quoted(args.front()) << std::endl;
cmd::print_close_matches(args.front());
std::exit(1);
}
break;
while (args.size()) if (auto failed = cmd::accept_commandline_argument(runnables, args)) {
std::cerr << "musique: error: Failed to recognize parameter " << std::quoted(*failed) << std::endl;
cmd::print_close_matches(args.front());
std::exit(1);
}
/*
if (arg == "--ast") {
ast_only_mode = true;
continue;
}
if (arg == "--version" || arg == "-v") {
std::cout << Musique_Version << std::endl;
return {};
}
*/
Runner runner;
for (auto const& [type, argument] : runnables) {