diff --git a/CMakeLists.txt b/CMakeLists.txt index 740a8aded..815c33677 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,3 +66,12 @@ add_library(devbnd SHARED src/binder.cpp) target_compile_options(devbnd PRIVATE -D_GNU_SOURCE -Wall -Wextra) target_link_options(devbnd PRIVATE -nostartfiles) target_link_libraries(devbnd dl) + +#################### CLI EXECUTABLE #################### +add_executable(nr-cli src/cli.cpp) +target_link_libraries(nr-cli pthread) +target_compile_options(nr-cli PRIVATE -Wall -Wextra -pedantic) + +target_link_libraries(nr-cli app) +target_link_libraries(nr-cli udp) +target_link_libraries(nr-cli utils) diff --git a/README.md b/README.md index 667868ba9..790c78077 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

diff --git a/makefile b/makefile index 3c971cbaa..26eb89847 100644 --- a/makefile +++ b/makefile @@ -13,6 +13,7 @@ build: FORCE cp cmake-build-release/nr-gnb build/ cp cmake-build-release/nr-ue build/ + cp cmake-build-release/nr-cli build/ cp cmake-build-release/libdevbnd.so build/ cp tools/nr-binder build/ diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 1902d7d15..25beaa69c 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -8,3 +8,4 @@ add_library(app ${HDR_FILES} ${SRC_FILES}) target_compile_options(app PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter) target_link_libraries(app utils) +target_link_libraries(app udp) diff --git a/src/app/base_app.cpp b/src/app/base_app.cpp new file mode 100644 index 000000000..9ae6e052f --- /dev/null +++ b/src/app/base_app.cpp @@ -0,0 +1,56 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "base_app.hpp" + +#include +#include +#include +#include +#include +#include +#include + +static std::atomic_int g_instanceCount{}; +static std::vector g_runAtExit{}; +static std::vector g_deleteAtExit{}; + +extern "C" void BaseSignalHandler(int num) +{ + for (auto &fun : g_runAtExit) + fun(); + for (auto &file : g_deleteAtExit) + std::remove(file.c_str()); + + if (num == SIGTERM || num == SIGINT) + exit(0); +} + +namespace app +{ + +void Initialize() +{ + if (g_instanceCount++ != 0) + std::terminate(); + + std::signal(SIGTERM, BaseSignalHandler); + std::signal(SIGINT, BaseSignalHandler); +} + +void RunAtExit(void (*fun)()) +{ + g_runAtExit.push_back(fun); +} + +void DeleteAtExit(const std::string &file) +{ + g_deleteAtExit.push_back(file); +} + +} // namespace app diff --git a/src/app/base_app.hpp b/src/app/base_app.hpp new file mode 100644 index 000000000..f1a2a586f --- /dev/null +++ b/src/app/base_app.hpp @@ -0,0 +1,24 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include + +namespace app +{ + +void Initialize(); + +void RunAtExit(void (*fun)()); + +void DeleteAtExit(const std::string &file); + +} // namespace app diff --git a/src/app/cli_base.cpp b/src/app/cli_base.cpp new file mode 100644 index 000000000..d0673bc0d --- /dev/null +++ b/src/app/cli_base.cpp @@ -0,0 +1,69 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "cli_base.hpp" +#include +#include + +#define CMD_BUFFER_SIZE 8192 +#define CMD_RCV_TIMEOUT 2500 +#define CMD_MIN_LENGTH (3 + 4 + 4 + 1) + +namespace app +{ + +InetAddress CliServer::assignedAddress() const +{ + return m_socket.getAddress(); +} + +CliMessage CliServer::receiveMessage() +{ + uint8_t buffer[CMD_BUFFER_SIZE] = {0}; + InetAddress address; + + int size = m_socket.receive(buffer, CMD_BUFFER_SIZE, CMD_RCV_TIMEOUT, address); + if (size < CMD_MIN_LENGTH || size >= CMD_BUFFER_SIZE) + return {}; + + OctetView v{buffer, static_cast(size)}; + if (v.readI() != cons::Major) + return {}; + if (v.readI() != cons::Minor) + return {}; + if (v.readI() != cons::Patch) + return {}; + + CliMessage res{}; + res.type = static_cast(v.readI()); + int nodeNameLength = v.read4I(); + res.nodeName = v.readUtf8String(nodeNameLength); + int valueLength = v.read4I(); + res.value = v.readUtf8String(valueLength); + res.clientAddr = address; + return res; +} + +void CliServer::sendMessage(const CliMessage &msg) +{ + OctetString stream{}; + stream.appendOctet(cons::Major); + stream.appendOctet(cons::Minor); + stream.appendOctet(cons::Patch); + stream.appendOctet(static_cast(msg.type)); + stream.appendOctet4(static_cast(msg.nodeName.size())); + for (char c : msg.nodeName) + stream.appendOctet(static_cast(c)); + stream.appendOctet4(static_cast(msg.value.size())); + for (char c : msg.value) + stream.appendOctet(static_cast(c)); + + m_socket.send(msg.clientAddr, stream.data(), static_cast(stream.length())); +} + +} // namespace app diff --git a/src/app/cli_base.hpp b/src/app/cli_base.hpp new file mode 100644 index 000000000..a10a0b7fe --- /dev/null +++ b/src/app/cli_base.hpp @@ -0,0 +1,143 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace app +{ + +struct CliMessage +{ + enum class Type + { + EMPTY = 0, + ECHO, + ERROR, + RESULT, + COMMAND + } type{}; + + std::string nodeName{}; + std::string value{}; + InetAddress clientAddr{}; + + static CliMessage Error(InetAddress addr, std::string msg, std::string node = "") + { + CliMessage m{}; + m.type = Type::ERROR; + m.value = std::move(msg); + m.nodeName = std::move(node); + m.clientAddr = addr; + return m; + } + + static CliMessage Result(InetAddress addr, std::string msg, std::string node = "") + { + CliMessage m{}; + m.type = Type::RESULT; + m.value = std::move(msg); + m.nodeName = std::move(node); + m.clientAddr = addr; + return m; + } + + static CliMessage Echo(InetAddress addr, std::string msg) + { + CliMessage m{}; + m.type = Type::ECHO; + m.value = std::move(msg); + m.nodeName = ""; + m.clientAddr = addr; + return m; + } + + static CliMessage Command(InetAddress addr, std::string msg, std::string node = "") + { + CliMessage m{}; + m.type = Type::COMMAND; + m.value = std::move(msg); + m.nodeName = std::move(node); + m.clientAddr = addr; + return m; + } +}; + +class CliServer +{ + private: + Socket m_socket; + + public: + explicit CliServer() : m_socket{Socket::CreateAndBindUdp({cons::CMD_SERVER_IP, 0})} + { + } + + ~CliServer() + { + m_socket.close(); + } + + [[nodiscard]] InetAddress assignedAddress() const; + + CliMessage receiveMessage(); + void sendMessage(const CliMessage &msg); +}; + +struct NwCliSendResponse : NtsMessage +{ + InetAddress address{}; + std::string output{}; + bool isError{}; + + NwCliSendResponse(const InetAddress &address, std::string output, bool isError) + : NtsMessage(NtsMessageType::CLI_SEND_RESPONSE), address(address), output(std::move(output)), isError(isError) + { + } +}; + +class CliResponseTask : public NtsTask +{ + private: + app::CliServer *cliServer; + + public: + explicit CliResponseTask(CliServer *cliServer) : cliServer(cliServer) + { + } + + protected: + void onStart() override + { + } + void onLoop() override + { + auto *msg = take(); + if (msg == nullptr) + return; + if (msg->msgType == NtsMessageType::CLI_SEND_RESPONSE) + { + auto *w = dynamic_cast(msg); + cliServer->sendMessage(w->isError ? CliMessage::Error(w->address, w->output) + : CliMessage::Result(w->address, w->output)); + } + delete msg; + } + void onQuit() override + { + } +}; + +} // namespace app diff --git a/src/app/cli_cmd.cpp b/src/app/cli_cmd.cpp new file mode 100644 index 000000000..25d377cc6 --- /dev/null +++ b/src/app/cli_cmd.cpp @@ -0,0 +1,231 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "cli_cmd.hpp" +#include +#include +#include +#include +#include +#include + +#define CMD_ERR(x) \ + { \ + error = x; \ + return nullptr; \ + } + +static opt::OptionsDescription Desc(const std::string &subCommand, const std::string &desc, const std::string &usage, + bool helpIfEmpty) +{ + return {subCommand, cons::Tag, desc, {}, subCommand, {usage}, helpIfEmpty}; +} + +class OptionsHandler : public opt::IOptionsHandler +{ + public: + std::stringstream m_output{}; + std::stringstream m_err{}; + + public: + std::ostream &ostream(bool isError) override + { + return isError ? m_err : m_output; + } + + void status(int code) override + { + // nothing to do + } +}; + +static std::string DumpCommands(const std::map &descTable) +{ + size_t maxLength = 0; + for (auto &item : descTable) + maxLength = std::max(maxLength, item.first.size()); + + std::stringstream ss{}; + for (auto &item : descTable) + ss << item.first << std::string(maxLength - item.first.size(), ' ') << " | " << item.second << "\n"; + std::string output = ss.str(); + utils::Trim(output); + return output; +} + +namespace app +{ + +static std::map g_gnbCmdToDescription = { + {"status", "Show some status information about the gNB"}, + {"info", "Show some information about the gNB"}, + {"amf-list", "List all AMFs associated with the gNB"}, + {"amf-info", "Show some status information about the given AMF"}, + {"ue-list", "List all UEs associated with the gNB"}, + {"ue-count", "Print the total number of UEs connected the this gNB"}, +}; + +static std::map g_gnbCmdToUsage = { + {"status", "[option...]"}, {"info", "[option...]"}, + {"amf-list", "[option...]"}, {"amf-info", " [option...]"}, + {"ue-list", "[option...]"}, {"ue-count", "[option...]"}, +}; + +static std::map g_gnbCmdToHelpIfEmpty = {{"status", false}, {"info", false}, + {"amf-list", false}, {"amf-info", true}, + {"ue-list", false}, {"ue-count", false}}; + +static std::map g_ueCmdToDescription = { + {"info", "Show some information about the UE"}, + {"status", "Show some status information about the UE"}, + {"timers", "Dump current status of the timers in the UE"}, +}; + +static std::map g_ueCmdToUsage = { + {"info", "[option...]"}, + {"status", "[option...]"}, + {"timers", "[option...]"}, +}; + +static std::map g_ueCmdToHelpIfEmpty = { + {"info", false}, + {"status", false}, + {"timers", false}, +}; + +std::unique_ptr ParseGnbCliCommand(std::vector &&tokens, std::string &error, + std::string &output) +{ + if (tokens.empty()) + { + error = "Empty command"; + return nullptr; + } + + std::string &subCmd = tokens[0]; + + if (subCmd == "commands") + { + output = DumpCommands(g_gnbCmdToDescription); + return nullptr; + } + + if (g_gnbCmdToDescription.count(subCmd) == 0 || g_gnbCmdToUsage.count(subCmd) == 0 || + g_gnbCmdToHelpIfEmpty.count(subCmd) == 0) + { + error = "Command not recognized: " + subCmd; + return nullptr; + } + + opt::OptionsDescription desc = + Desc(subCmd, g_gnbCmdToDescription[subCmd], g_gnbCmdToUsage[subCmd], g_gnbCmdToHelpIfEmpty[subCmd]); + + OptionsHandler handler{}; + + opt::OptionsResult options{tokens, desc, &handler}; + + error = handler.m_err.str(); + output = handler.m_output.str(); + utils::Trim(error); + utils::Trim(output); + + if (!error.empty() || !output.empty()) + return nullptr; + + if (subCmd == "info") + { + return std::make_unique(GnbCliCommand::INFO); + } + if (subCmd == "status") + { + return std::make_unique(GnbCliCommand::STATUS); + } + else if (subCmd == "amf-list") + { + return std::make_unique(GnbCliCommand::AMF_LIST); + } + else if (subCmd == "amf-info") + { + auto cmd = std::make_unique(GnbCliCommand::AMF_INFO); + if (options.positionalCount() == 0) + CMD_ERR("AMF ID is expected") + if (options.positionalCount() > 1) + CMD_ERR("Only one AMF ID is expected") + cmd->amfId = utils::ParseInt(options.getPositional(0)); + if (cmd->amfId <= 0) + CMD_ERR("Invalid AMF ID") + return cmd; + } + else if (subCmd == "ue-list") + { + return std::make_unique(GnbCliCommand::UE_LIST); + } + else if (subCmd == "ue-count") + { + return std::make_unique(GnbCliCommand::UE_COUNT); + } + + return nullptr; +} + +std::unique_ptr ParseUeCliCommand(std::vector &&tokens, std::string &error, + std::string &output) +{ + if (tokens.empty()) + { + error = "Empty command"; + return nullptr; + } + + std::string &subCmd = tokens[0]; + + if (subCmd == "commands") + { + output = DumpCommands(g_ueCmdToDescription); + return nullptr; + } + + if (g_ueCmdToDescription.count(subCmd) == 0 || g_ueCmdToUsage.count(subCmd) == 0 || + g_ueCmdToHelpIfEmpty.count(subCmd) == 0) + { + error = "Command not recognized: " + subCmd; + return nullptr; + } + + opt::OptionsDescription desc = + Desc(subCmd, g_ueCmdToDescription[subCmd], g_ueCmdToUsage[subCmd], g_ueCmdToHelpIfEmpty[subCmd]); + + OptionsHandler handler{}; + + opt::OptionsResult options{tokens, desc, &handler}; + + error = handler.m_err.str(); + output = handler.m_output.str(); + utils::Trim(error); + utils::Trim(output); + + if (!error.empty() || !output.empty()) + return nullptr; + + if (subCmd == "info") + { + return std::make_unique(UeCliCommand::INFO); + } + else if (subCmd == "status") + { + return std::make_unique(UeCliCommand::STATUS); + } + else if (subCmd == "timers") + { + return std::make_unique(UeCliCommand::TIMERS); + } + + return nullptr; +} + +} // namespace app diff --git a/src/app/cli_cmd.hpp b/src/app/cli_cmd.hpp new file mode 100644 index 000000000..f3e8dbf05 --- /dev/null +++ b/src/app/cli_cmd.hpp @@ -0,0 +1,58 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include + +namespace app +{ + +struct GnbCliCommand +{ + enum PR + { + STATUS, + INFO, + AMF_LIST, + AMF_INFO, + UE_LIST, + UE_COUNT + } present; + + // AMF_INFO + int amfId{}; + + explicit GnbCliCommand(PR present) : present(present) + { + } +}; + +struct UeCliCommand +{ + enum PR + { + INFO, + STATUS, + TIMERS, + } present; + + explicit UeCliCommand(PR present) : present(present) + { + } +}; + +std::unique_ptr ParseGnbCliCommand(std::vector &&tokens, std::string &error, + std::string &output); + +std::unique_ptr ParseUeCliCommand(std::vector &&tokens, std::string &error, + std::string &output); + +} // namespace app \ No newline at end of file diff --git a/src/app/proc_table.cpp b/src/app/proc_table.cpp new file mode 100644 index 000000000..3c85a297c --- /dev/null +++ b/src/app/proc_table.cpp @@ -0,0 +1,97 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include "proc_table.hpp" +#include "base_app.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace app +{ + +void CreateProcTable(const std::vector &nodes, int cmdPort) +{ + try + { + io::CreateDirectory(cons::PROC_TABLE_DIR); + + std::string filePath; + while (true) + { + std::string fileName = utils::IntToHex(utils::Random64()) + utils::IntToHex(utils::Random64()); + filePath = cons::PROC_TABLE_DIR + fileName; + + if (!io::Exists(filePath)) + break; + } + + ProcTableEntry entry{}; + entry.major = cons::Major; + entry.minor = cons::Minor; + entry.patch = cons::Patch; + entry.pid = static_cast(::getpid()); + entry.port = cmdPort; + entry.nodes = nodes; + + io::WriteAllText(filePath, ProcTableEntry::Encode(entry)); + + DeleteAtExit(filePath); + } + catch (const std::runtime_error &e) + { + throw std::runtime_error("ProcTable could not be created: " + std::string(e.what())); + } +} + +std::string ProcTableEntry::Encode(const ProcTableEntry &e) +{ + std::stringstream ss{""}; + ss << e.major << " "; + ss << e.minor << " "; + ss << e.patch << " "; + ss << e.pid << " "; + ss << e.port << " "; + ss << e.nodes.size() << " "; + for (auto &n : e.nodes) + { + utils::AssertNodeName(n); + ss << n << " "; + } + return ss.str(); +} + +ProcTableEntry ProcTableEntry::Decode(const std::string &s) +{ + ProcTableEntry e{}; + std::stringstream ss{s}; + + ss >> e.major; + ss >> e.minor; + ss >> e.patch; + ss >> e.pid; + ss >> e.port; + + size_t nodeSize = 0; + ss >> nodeSize; + for (size_t i = 0; i < nodeSize; i++) + { + std::string n{}; + ss >> n; + e.nodes.push_back(std::move(n)); + } + + return e; +} + +} // namespace app \ No newline at end of file diff --git a/src/app/proc_table.hpp b/src/app/proc_table.hpp new file mode 100644 index 000000000..ef67523b2 --- /dev/null +++ b/src/app/proc_table.hpp @@ -0,0 +1,44 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#pragma once + +#include +#include +#include + +namespace app +{ + +struct ProcTableEntry +{ + int major{}; + int minor{}; + int patch{}; + int pid{}; + uint16_t port{}; + std::vector nodes{}; + + // ... (New members must always be added to the end) + + static std::string Encode(const ProcTableEntry &e); + static ProcTableEntry Decode(const std::string &s); +}; + +void CreateProcTable(const std::vector &nodes, int cmdPort); + +template +inline void CreateProcTable(const std::unordered_map &nodeMap, int cmdPort) +{ + std::vector nodes{}; + for (auto &node : nodeMap) + nodes.push_back(node.first); + CreateProcTable(nodes, cmdPort); +} + +} // namespace app diff --git a/src/asn/utils/utils.cpp b/src/asn/utils/utils.cpp index d720d1af2..f90b0b4f5 100644 --- a/src/asn/utils/utils.cpp +++ b/src/asn/utils/utils.cpp @@ -85,7 +85,7 @@ std::string GetPrintableString(const PrintableString_t &source) { std::string r(source.size, '0'); for (size_t i = 0; i < source.size; i++) - r += (char)source.buf[i]; + r[i] = (char)source.buf[i]; return r; } diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 000000000..bb66fa455 --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,307 @@ +// +// This file is a part of UERANSIM open source project. +// Copyright (c) 2021 ALİ GÜNGÖR. +// +// The software and all associated files are licensed under GPL-3.0 +// and subject to the terms and conditions defined in LICENSE file. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct Options +{ + bool dumpNodes{}; + std::string nodeName{}; + std::string directCmd{}; +} g_options{}; + +static std::set FindProcesses() +{ + std::set res{}; + for (const auto &file : io::GetEntries(cons::PROCESS_DIR)) + { + if (!io::IsRegularFile(file)) + { + auto name = io::GetStem(file); + if (!utils::IsNumeric(name)) + continue; + int pid = utils::ParseInt(name); + res.insert(pid); + } + } + return res; +} + +static uint16_t DiscoverNode(const std::string &node, int &skippedDueToVersion) +{ + if (!io::Exists(cons::PROC_TABLE_DIR)) + return 0; + + // Find all processes in the environment + auto processes = FindProcesses(); + + // Read and parse ProcTable entries. + std::unordered_map entries{}; + for (const auto &file : io::GetEntries(cons::PROC_TABLE_DIR)) + { + if (!io::IsRegularFile(file)) + continue; + std::string content = io::ReadAllText(file); + entries[file] = app::ProcTableEntry::Decode(content); + } + + uint16_t found = 0; + skippedDueToVersion = 0; + + for (auto &e : entries) + { + // If no such process, it means that this ProcTable file is outdated + // Therefore that file should be deleted + if (processes.count(e.second.pid) == 0) + { + io::Remove(e.first); + continue; + } + + // If searching node exists in this file, extract port number from it. + for (auto &n : e.second.nodes) + { + if (n == node) + { + if (e.second.major == cons::Major && e.second.minor == cons::Minor && e.second.patch == cons::Patch) + found = e.second.port; + else + skippedDueToVersion++; + } + } + } + + return found; +} + +static std::vector DumpNames() +{ + std::vector v{}; + + if (!io::Exists(cons::PROC_TABLE_DIR)) + return v; + + // Find all processes in the environment + auto processes = FindProcesses(); + + // Read and parse ProcTable entries. + std::unordered_map entries{}; + for (const auto &file : io::GetEntries(cons::PROC_TABLE_DIR)) + { + if (!io::IsRegularFile(file)) + continue; + std::string content = io::ReadAllText(file); + entries[file] = app::ProcTableEntry::Decode(content); + } + + for (auto &e : entries) + { + // If no such process, it means that this ProcTable file is outdated + // Therefore that file should be deleted + if (processes.count(e.second.pid) == 0) + { + io::Remove(e.first); + continue; + } + + for (auto &n : e.second.nodes) + v.push_back(n); + } + + std::sort(v.begin(), v.end()); + return v; +} + +static void ReadOptions(int argc, char **argv) +{ + opt::OptionsDescription desc{"UERANSIM", cons::Tag, "Command Line Interface", + cons::Owner, "nr-cli", {" [option...]", "--dump"}, + true}; + + opt::OptionItem itemDump = {'d', "dump", "List all UE and gNBs in the environment", std::nullopt}; + opt::OptionItem itemExec = {'e', "exec", "Execute the given command directly without an interactive shell", + "command"}; + + desc.items.push_back(itemDump); + desc.items.push_back(itemExec); + + opt::OptionsResult opt{argc, argv, desc, false, nullptr}; + + g_options.dumpNodes = opt.hasFlag(itemDump); + + if (!g_options.dumpNodes) + { + if (opt.positionalCount() == 0) + { + opt.showError("Node name is expected"); + return; + } + if (opt.positionalCount() > 1) + { + opt.showError("Only one node name is expected"); + return; + } + + g_options.nodeName = opt.getPositional(0); + if (g_options.nodeName.size() < cons::MinNodeName) + { + opt.showError("Node name is too short"); + return; + } + if (g_options.nodeName.size() > cons::MaxNodeName) + { + opt.showError("Node name is too long"); + return; + } + + g_options.directCmd = opt.getOption(itemExec); + if (opt.hasFlag(itemExec) && g_options.directCmd.size() < 3) + { + opt.showError("Command is too short"); + return; + } + } +} + +static bool HandleMessage(const app::CliMessage &msg, bool isOneShot) +{ + if (msg.type == app::CliMessage::Type::ERROR) + { + std::cerr << "ERROR: " << msg.value << std::endl; + if (isOneShot) + exit(1); + return true; + } + + if (msg.type == app::CliMessage::Type::ECHO) + { + std::cout << msg.value << std::endl; + return true; + } + + if (msg.type == app::CliMessage::Type::RESULT) + { + std::cout << msg.value << std::endl; + if (isOneShot) + exit(0); + return true; + } + + return false; +} + +[[noreturn]] static void SendCommand(uint16_t port) +{ + app::CliServer server{}; + + if (g_options.directCmd.empty()) + { + while (true) + { + std::cout << "\x1b[1m"; + std::cout << std::string(92, '-') << std::endl; + std::string line{}; + bool isEof{}; + std::vector tokens{}; + if (!opt::ReadLine(std::cin, std::cout, line, tokens, isEof)) + { + if (isEof) + exit(0); + else + std::cout << "ERROR: Invalid command" << std::endl; + } + std::cout << "\x1b[0m"; + if (line.empty()) + continue; + + server.sendMessage( + app::CliMessage::Command(InetAddress{cons::CMD_SERVER_IP, port}, line, g_options.nodeName)); + + while (!HandleMessage(server.receiveMessage(), false)) + { + // empty + } + } + } + else + { + server.sendMessage( + app::CliMessage::Command(InetAddress{cons::CMD_SERVER_IP, port}, g_options.directCmd, g_options.nodeName)); + + while (true) + HandleMessage(server.receiveMessage(), true); + } +} + +int main(int argc, char **argv) +{ + ReadOptions(argc, argv); + + // NOTE: This does not guarantee showing the exact realtime status. + if (g_options.dumpNodes) + { + for (auto &n : DumpNames()) + std::cout << n << "\n"; + std::cout.flush(); + exit(0); + } + + if (g_options.nodeName.empty()) + { + std::cerr << "ERROR: No node name is specified" << std::endl; + exit(1); + } + + if (g_options.nodeName.size() > cons::MaxNodeName) + { + std::cerr << "ERROR: Node name is too long" << std::endl; + exit(1); + } + + if (g_options.nodeName.size() < cons::MinNodeName) + { + std::cerr << "ERROR: Node name is too short" << std::endl; + exit(1); + } + + uint16_t cmdPort{}; + int skippedDueToVersion{}; + + try + { + cmdPort = DiscoverNode(g_options.nodeName, skippedDueToVersion); + } + catch (const std::runtime_error &e) + { + throw std::runtime_error("Node discovery failure: " + std::string{e.what()}); + } + + if (cmdPort == 0) + { + std::cerr << "ERROR: No node found with name: " << g_options.nodeName << std::endl; + if (skippedDueToVersion > 0) + std::cerr << "WARNING: " << skippedDueToVersion + << " node(s) skipped due to version mismatch between the node and the CLI" << std::endl; + return 1; + } + + SendCommand(cmdPort); + return 0; +} \ No newline at end of file diff --git a/src/ext/cxxopts/cxxopts.hpp b/src/ext/cxxopts/cxxopts.hpp deleted file mode 100644 index 01ce03524..000000000 --- a/src/ext/cxxopts/cxxopts.hpp +++ /dev/null @@ -1,2308 +0,0 @@ -/* - -Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -#ifndef CXXOPTS_HPP_INCLUDED -#define CXXOPTS_HPP_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __cpp_lib_optional -#include -#define CXXOPTS_HAS_OPTIONAL -#endif - -#if __cplusplus >= 201603L -#define CXXOPTS_NODISCARD [[nodiscard]] -#else -#define CXXOPTS_NODISCARD -#endif - -#ifndef CXXOPTS_VECTOR_DELIMITER -#define CXXOPTS_VECTOR_DELIMITER ',' -#endif - -#define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 0 -#define CXXOPTS__VERSION_PATCH 0 - -namespace cxxopts -{ - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; -} // namespace cxxopts - -//when we ask cxxopts to use Unicode, help strings are processed using ICU, -//which results in the correct lengths being computed for strings when they -//are formatted for the help output -//it is necessary to make sure that can be found by the -//compiler, and that icu-uc is linked in to the binary. - -#ifdef CXXOPTS_USE_UNICODE -#include - -namespace cxxopts -{ - using String = icu::UnicodeString; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } - - class UnicodeStringIterator : public - std::iterator - { - public: - - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) - { - } - - value_type - operator*() const - { - return s->char32At(i); - } - - bool - operator==(const UnicodeStringIterator& rhs) const - { - return s == rhs.s && i == rhs.i; - } - - bool - operator!=(const UnicodeStringIterator& rhs) const - { - return !(*this == rhs); - } - - UnicodeStringIterator& - operator++() - { - ++i; - return *this; - } - - UnicodeStringIterator - operator+(int32_t v) - { - return UnicodeStringIterator(s, i + v); - } - - private: - const icu::UnicodeString* s; - int32_t i; - }; - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, size_t n, UChar32 c) - { - for (size_t i = 0; i != n; ++i) - { - s.append(c); - } - - return s; - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) - { - s.append(*begin); - ++begin; - } - - return s; - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); - - return result; - } - - inline - bool - empty(const String& s) - { - return s.isEmpty(); - } -} - -namespace std -{ - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } -} - -//ifdef CXXOPTS_USE_UNICODE -#else - -namespace cxxopts -{ - using String = std::string; - - template - T - toLocalString(T&& t) - { - return std::forward(t); - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - String& - stringAppend(String&s, const String& a) - { - return s.append(a); - } - - inline - String& - stringAppend(String& s, size_t n, char c) - { - return s.append(n, c); - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - - inline - bool - empty(const std::string& s) - { - return s.empty(); - } -} // namespace cxxopts - -//ifdef CXXOPTS_USE_UNICODE -#endif - -namespace cxxopts -{ - namespace - { -#ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); -#else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); -#endif - } // namespace - -#if defined(__GNUC__) -// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: -// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic push -// This will be ignored under other compilers like LLVM clang. -#endif - class Value : public std::enable_shared_from_this - { - public: - - virtual ~Value() = default; - - virtual - std::shared_ptr - clone() const = 0; - - virtual void - parse(const std::string& text) const = 0; - - virtual void - parse() const = 0; - - virtual bool - has_default() const = 0; - - virtual bool - is_container() const = 0; - - virtual bool - has_implicit() const = 0; - - virtual std::string - get_default_value() const = 0; - - virtual std::string - get_implicit_value() const = 0; - - virtual std::shared_ptr - default_value(const std::string& value) = 0; - - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; - - virtual std::shared_ptr - no_implicit_value() = 0; - - virtual bool - is_boolean() const = 0; - }; -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - class OptionException : public std::exception - { - public: - explicit OptionException(std::string message) - : m_message(std::move(message)) - { - } - - CXXOPTS_NODISCARD - const char* - what() const noexcept override - { - return m_message.c_str(); - } - - private: - std::string m_message; - }; - - class OptionSpecException : public OptionException - { - public: - - explicit OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; - - class OptionParseException : public OptionException - { - public: - explicit OptionParseException(const std::string& message) - : OptionException(message) - { - } - }; - - class option_exists_error : public OptionSpecException - { - public: - explicit option_exists_error(const std::string& option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { - } - }; - - class invalid_option_format_error : public OptionSpecException - { - public: - explicit invalid_option_format_error(const std::string& format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) - { - } - }; - - class option_syntax_exception : public OptionParseException { - public: - explicit option_syntax_exception(const std::string& text) - : OptionParseException("Argument " + LQUOTE + text + RQUOTE + - " starts with a - but has incorrect syntax") - { - } - }; - - class option_not_exists_exception : public OptionParseException - { - public: - explicit option_not_exists_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") - { - } - }; - - class missing_argument_exception : public OptionParseException - { - public: - explicit missing_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is missing an argument" - ) - { - } - }; - - class option_requires_argument_exception : public OptionParseException - { - public: - explicit option_requires_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " requires an argument" - ) - { - } - }; - - class option_not_has_argument_exception : public OptionParseException - { - public: - option_not_has_argument_exception - ( - const std::string& option, - const std::string& arg - ) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + - " does not take an argument, but argument " + - LQUOTE + arg + RQUOTE + " given" - ) - { - } - }; - - class option_not_present_exception : public OptionParseException - { - public: - explicit option_not_present_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") - { - } - }; - - class option_has_no_value_exception : public OptionException - { - public: - explicit option_has_no_value_exception(const std::string& option) - : OptionException( - option.empty() ? - ("Option " + LQUOTE + option + RQUOTE + " has no value") : - "Option has no value") - { - } - }; - - class argument_incorrect_type : public OptionParseException - { - public: - explicit argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) - { - } - }; - - class option_required_exception : public OptionParseException - { - public: - explicit option_required_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is required but not present" - ) - { - } - }; - - template - void throw_or_mimic(const std::string& text) - { - static_assert(std::is_base_of::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); - -#ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{text}; -#else - // Otherwise manually instantiate the exception, print what() to stderr, - // and exit - T exception{text}; - std::cerr << exception.what() << std::endl; - std::exit(EXIT_FAILURE); -#endif - } - - namespace values - { - namespace - { - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); - } // namespace - - namespace detail - { - template - struct SignedCheck; - - template - struct SignedCheck - { - template - void - operator()(bool negative, U u, const std::string& text) - { - if (negative) - { - if (u > static_cast((std::numeric_limits::min)())) - { - throw_or_mimic(text); - } - } - else - { - if (u > static_cast((std::numeric_limits::max)())) - { - throw_or_mimic(text); - } - } - } - }; - - template - struct SignedCheck - { - template - void - operator()(bool, U, const std::string&) const {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck::is_signed>()(negative, value, text); - } - } // namespace detail - - template - void - checked_negate(R& r, T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - r = static_cast(-static_cast(t-1)-1); - } - - template - void - checked_negate(R&, T&&, const std::string& text, std::false_type) - { - throw_or_mimic(text); - } - - template - void - integer_parser(const std::string& text, T& value) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); - - if (match.length() == 0) - { - throw_or_mimic(text); - } - - if (match.length(4) > 0) - { - value = 0; - return; - } - - using US = typename std::make_unsigned::type; - - constexpr bool is_signed = std::numeric_limits::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; - - auto value_match = match[3]; - - US result = 0; - - for (auto iter = value_match.first; iter != value_match.second; ++iter) - { - US digit = 0; - - if (*iter >= '0' && *iter <= '9') - { - digit = static_cast(*iter - '0'); - } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') - { - digit = static_cast(*iter - 'a' + 10); - } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') - { - digit = static_cast(*iter - 'A' + 10); - } - else - { - throw_or_mimic(text); - } - - const US next = static_cast(result * base + digit); - if (result > next) - { - throw_or_mimic(text); - } - - result = next; - } - - detail::check_signed_range(negative, result, text); - - if (negative) - { - checked_negate(value, result, text, std::integral_constant()); - } - else - { - value = static_cast(result); - } - } - - template - void stringstream_parser(const std::string& text, T& value) - { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } - } - - inline - void - parse_value(const std::string& text, uint8_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int8_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint16_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int16_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int32_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, bool& value) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - - if (!result.empty()) - { - value = true; - return; - } - - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) - { - value = false; - return; - } - - throw_or_mimic(text); - } - - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } - - // The fallback parser. It uses the stringstream parser to parse all types - // that have not been overloaded explicitly. It has to be placed in the - // source code before all other more specialized templates. - template - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); - } - - template - void - parse_value(const std::string& text, std::vector& value) - { - std::stringstream in(text); - std::string token; - while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } - } - -#ifdef CXXOPTS_HAS_OPTIONAL - template - void - parse_value(const std::string& text, std::optional& value) - { - T result; - parse_value(text, result); - value = std::move(result); - } -#endif - - inline - void parse_value(const std::string& text, char& c) - { - if (text.length() != 1) - { - throw_or_mimic(text); - } - - c = text[0]; - } - - template - struct type_is_container - { - static constexpr bool value = false; - }; - - template - struct type_is_container> - { - static constexpr bool value = true; - }; - - template - class abstract_value : public Value - { - using Self = abstract_value; - - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } - - explicit abstract_value(T* t) - : m_store(t) - { - } - - ~abstract_value() override = default; - - abstract_value& operator=(const abstract_value&) = default; - - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) - { - m_result = std::make_shared(); - m_store = m_result.get(); - } - else - { - m_store = rhs.m_store; - } - - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } - - void - parse(const std::string& text) const override - { - parse_value(text, *m_store); - } - - bool - is_container() const override - { - return type_is_container::value; - } - - void - parse() const override - { - parse_value(m_default_value, *m_store); - } - - bool - has_default() const override - { - return m_default; - } - - bool - has_implicit() const override - { - return m_implicit; - } - - std::shared_ptr - default_value(const std::string& value) override - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - std::shared_ptr - implicit_value(const std::string& value) override - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::shared_ptr - no_implicit_value() override - { - m_implicit = false; - return shared_from_this(); - } - - std::string - get_default_value() const override - { - return m_default_value; - } - - std::string - get_implicit_value() const override - { - return m_implicit_value; - } - - bool - is_boolean() const override - { - return std::is_same::value; - } - - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - return *m_store; - } - - protected: - std::shared_ptr m_result{}; - T* m_store{}; - - bool m_default = false; - bool m_implicit = false; - - std::string m_default_value{}; - std::string m_implicit_value{}; - }; - - template - class standard_value : public abstract_value - { - public: - using abstract_value::abstract_value; - - CXXOPTS_NODISCARD - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - }; - - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() override = default; - - standard_value() - { - set_default_and_implicit(); - } - - explicit standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } - - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - - private: - - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; - } // namespace values - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - std::string short_, - std::string long_, - String desc, - std::shared_ptr val - ) - : m_short(std::move(short_)) - , m_long(std::move(long_)) - , m_desc(std::move(desc)) - , m_value(std::move(val)) - , m_count(0) - { - m_hash = std::hash{}(m_long + m_short); - } - - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_value(rhs.m_value->clone()) - , m_count(rhs.m_count) - { - } - - OptionDetails(OptionDetails&& rhs) = default; - - CXXOPTS_NODISCARD - const String& - description() const - { - return m_desc; - } - - CXXOPTS_NODISCARD - const Value& - value() const { - return *m_value; - } - - CXXOPTS_NODISCARD - std::shared_ptr - make_storage() const - { - return m_value->clone(); - } - - CXXOPTS_NODISCARD - const std::string& - short_name() const - { - return m_short; - } - - CXXOPTS_NODISCARD - const std::string& - long_name() const - { - return m_long; - } - - size_t - hash() const - { - return m_hash; - } - - private: - std::string m_short{}; - std::string m_long{}; - String m_desc{}; - std::shared_ptr m_value{}; - int m_count; - - size_t m_hash{}; - }; - - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; - - struct HelpGroupDetails - { - std::string name{}; - std::string description{}; - std::vector options{}; - }; - - class OptionValue - { - public: - void - parse - ( - const std::shared_ptr& details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - m_long_name = &details->long_name(); - } - - void - parse_default(const std::shared_ptr& details) - { - ensure_value(details); - m_default = true; - m_long_name = &details->long_name(); - m_value->parse(); - } - -#if defined(__GNUC__) -#if __GNUC__ <= 10 && __GNUC_MINOR__ <= 1 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Werror=null-dereference" -#endif -#endif - - CXXOPTS_NODISCARD - size_t - count() const noexcept - { - return m_count; - } - -#if defined(__GNUC__) -#if __GNUC__ <= 10 && __GNUC_MINOR__ <= 1 -#pragma GCC diagnostic pop -#endif -#endif - - // TODO: maybe default options should count towards the number of arguments - CXXOPTS_NODISCARD - bool - has_default() const noexcept - { - return m_default; - } - - template - const T& - as() const - { - if (m_value == nullptr) { - throw_or_mimic( - m_long_name == nullptr ? "" : *m_long_name); - } - -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } - - private: - void - ensure_value(const std::shared_ptr& details) - { - if (m_value == nullptr) - { - m_value = details->make_storage(); - } - } - - - const std::string* m_long_name = nullptr; - // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, - // where the key has the string we point to. - std::shared_ptr m_value{}; - size_t m_count = 0; - bool m_default = false; - }; - - class KeyValue - { - public: - KeyValue(std::string key_, std::string value_) - : m_key(std::move(key_)) - , m_value(std::move(value_)) - { - } - - CXXOPTS_NODISCARD - const std::string& - key() const - { - return m_key; - } - - CXXOPTS_NODISCARD - const std::string& - value() const - { - return m_value; - } - - template - T - as() const - { - T result; - values::parse_value(m_value, result); - return result; - } - - private: - std::string m_key; - std::string m_value; - }; - - using ParsedHashMap = std::unordered_map; - using NameHashMap = std::unordered_map; - - class ParseResult - { - public: - - ParseResult() = default; - ParseResult(const ParseResult&) = default; - - ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& unmatched_args) - : m_keys(std::move(keys)) - , m_values(std::move(values)) - , m_sequential(std::move(sequential)) - , m_unmatched(std::move(unmatched_args)) - { - } - - ParseResult& operator=(ParseResult&&) = default; - ParseResult& operator=(const ParseResult&) = default; - - size_t - count(const std::string& o) const - { - auto iter = m_keys.find(o); - if (iter == m_keys.end()) - { - return 0; - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) - { - return 0; - } - - return viter->second.count(); - } - - const OptionValue& - operator[](const std::string& option) const - { - auto iter = m_keys.find(option); - - if (iter == m_keys.end()) - { - throw_or_mimic(option); - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) - { - throw_or_mimic(option); - } - - return viter->second; - } - - const std::vector& - arguments() const - { - return m_sequential; - } - - const std::vector& - unmatched() const - { - return m_unmatched; - } - - private: - NameHashMap m_keys{}; - ParsedHashMap m_values{}; - std::vector m_sequential{}; - std::vector m_unmatched{}; - }; - - struct Option - { - Option - ( - std::string opts, - std::string desc, - std::shared_ptr value = ::cxxopts::value(), - std::string arg_help = "" - ) - : opts_(std::move(opts)) - , desc_(std::move(desc)) - , value_(std::move(value)) - , arg_help_(std::move(arg_help)) - { - } - - std::string opts_; - std::string desc_; - std::shared_ptr value_; - std::string arg_help_; - }; - - using OptionMap = std::unordered_map>; - using PositionalList = std::vector; - using PositionalListIterator = PositionalList::const_iterator; - - class OptionParser - { - public: - OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) - : m_options(options) - , m_positional(positional) - , m_allow_unrecognised(allow_unrecognised) - { - } - - ParseResult - parse(int argc, const char* const* argv); - - bool - consume_positional(const std::string& a, PositionalListIterator& next); - - void - checked_parse_arg - ( - int argc, - const char* const* argv, - int& current, - const std::shared_ptr& value, - const std::string& name - ); - - void - add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); - - void - parse_option - ( - const std::shared_ptr& value, - const std::string& name, - const std::string& arg = "" - ); - - void - parse_default(const std::shared_ptr& details); - - private: - - void finalise_aliases(); - - const OptionMap& m_options; - const PositionalList& m_positional; - - std::vector m_sequential{}; - bool m_allow_unrecognised; - - ParsedHashMap m_parsed{}; - NameHashMap m_keys{}; - }; - - class Options - { - public: - - explicit Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , m_help_string(toLocalString(std::move(help_string))) - , m_custom_help("[OPTION...]") - , m_positional_help("positional parameters") - , m_show_positional(false) - , m_allow_unrecognised(false) - , m_options(std::make_shared()) - { - } - - Options& - positional_help(std::string help_text) - { - m_positional_help = std::move(help_text); - return *this; - } - - Options& - custom_help(std::string help_text) - { - m_custom_help = std::move(help_text); - return *this; - } - - Options& - show_positional_help() - { - m_show_positional = true; - return *this; - } - - Options& - allow_unrecognised_options() - { - m_allow_unrecognised = true; - return *this; - } - - ParseResult - parse(int argc, const char* const* argv); - - OptionAdder - add_options(std::string group = ""); - - void - add_options - ( - const std::string& group, - std::initializer_list