From 83ede4ef813ba2069174a732d511a99cc64285f2 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Mon, 20 Feb 2023 18:16:20 +0100 Subject: [PATCH] Support complex syntax for command line arguments Support parameters prefixed by '-' or '--'. Support parameters in form 'key=value' --- config.mk | 2 +- musique/cmd.cc | 114 +++++++++++++++++++++++++++++++++--------------- musique/cmd.hh | 3 +- musique/main.cc | 26 +++-------- 4 files changed, 89 insertions(+), 56 deletions(-) diff --git a/config.mk b/config.mk index 2a6688d..52b23f3 100644 --- a/config.mk +++ b/config.mk @@ -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 diff --git a/musique/cmd.cc b/musique/cmd.cc index 6778968..5c189d3 100644 --- a/musique/cmd.cc +++ b/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 &runnables, std::span &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 cmd::accept_commandline_argument(std::vector &runnables, std::span &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 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 &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(); } diff --git a/musique/cmd.hh b/musique/cmd.hh index 8285be7..6f9af6f 100644 --- a/musique/cmd.hh +++ b/musique/cmd.hh @@ -1,6 +1,7 @@ #ifndef MUSIQUE_CMD_HH #define MUSIQUE_CMD_HH +#include #include #include #include @@ -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 &runnables, std::span &args); + std::optional accept_commandline_argument(std::vector &runnables, std::span &args); /// Print all arguments that are similar to one provided void print_close_matches(std::string_view arg); diff --git a/musique/main.cc b/musique/main.cc index ccb1034..81b222a 100644 --- a/musique/main.cc +++ b/musique/main.cc @@ -321,35 +321,21 @@ static Result handle_repl_session_commands(std::string_view input, Runner /// Fancy main that supports Result forwarding on error (Try macro) static std::optional Main(std::span args) { + enable_repl = args.empty(); + if (cmd::is_tty() && getenv("NO_COLOR") == nullptr) { pretty::terminal_mode(); } - std::vector 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) {