Support complex syntax for command line arguments
Support parameters prefixed by '-' or '--'. Support parameters in form 'key=value'
This commit is contained in:
parent
aaf6e6ec0c
commit
83ede4ef81
@ -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
|
||||
|
||||
|
114
musique/cmd.cc
114
musique/cmd.cc
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user