From 0e7993c19490d7bf91f7e766e4fd339a145a2be8 Mon Sep 17 00:00:00 2001 From: Christoph Heiss Date: Wed, 7 Mar 2018 21:27:54 +0100 Subject: [PATCH] Completely rework and rewrite command line argument parsing. Also fix some things: To actually set a variable to the value of the argument, & clipp::value must be used, not .set(). --- include/optional.h | 92 +++++++++++++++++++++++++++++++++ src/grpc-cli.cc | 11 ++-- src/proto-cli.cc | 35 +++++++------ src/proxy.cc | 124 +++++++++++++++++++++++++++------------------ src/resply-cli.cc | 7 ++- 5 files changed, 195 insertions(+), 74 deletions(-) create mode 100644 include/optional.h diff --git a/include/optional.h b/include/optional.h new file mode 100644 index 0000000..f61d01b --- /dev/null +++ b/include/optional.h @@ -0,0 +1,92 @@ +// +// Copyright 2018 Christoph Heiss +// Distributed under the Boost Software License, Version 1.0. +// +// See accompanying file LICENSE in the project root directory +// or copy at http://www.boost.org/LICENSE_1_0.txt +// + +#pragma once + + +/*! \brief Very simple re-implementation of std::{experimental::}optional. + * \param T Type of the value the optional should hold. + * + * Until std::{experimental::}optional is standardized, this can be used as a + * very simple and dumb replacement. + */ +template +class Optional { +public: + /*! \brief Type of the value the optional holds. */ + typedef T value_type; + + /*! \brief Constructs a new, empty optional. + * + * The default value will be default-constructed. + */ + Optional() : has_value_{}, default_value_{} { } + + /*! \brief Constructos a new optional with an default value. + * \param default_value The default value the optional will hold. + */ + Optional(T default_value) : has_value_{}, default_value_{default_value} { } + + /*! \brief Indicates if the optional holds a value. + * \return If the optional holds a value. + */ + bool has_value() const + { + return has_value_; + } + + /*! \brief Gets the value. + * \return The value the optional holds. + * + * If the optional has no value, a default-constructed object is returned. + */ + const T& value() const& + { + return value_; + } + + /*! \brief Returns the value if the optional has one, otherwise the default value. + * \return The value of the optional if it has one, otherwise the default value. + */ + const T& value_or_default() const& + { + return has_value() ? value() : default_value(); + } + + /*! \brief Sets the value of the optional. + * \param val The new value of the optional. + * \return The new value of the optional. + */ + const T& set_value(T&& val) + { + has_value_ = true; + value_ = std::move(val); + return value(); + } + + /*! \brief Returns the default value of the optional. + * \return The default value of the optional. + */ + const T& default_value() const& + { + return default_value_; + } + +private: + /*! \brief Indicates if this optional has a value set. */ + bool has_value_; + + /*! \brief The value that may or may not be present. + * + * Its present is indicated by #has_value_ + */ + T value_; + + /*! \brief The default value of this optional. */ + const T default_value_; +}; diff --git a/src/grpc-cli.cc b/src/grpc-cli.cc index 6bd9ae7..d869940 100644 --- a/src/grpc-cli.cc +++ b/src/grpc-cli.cc @@ -24,15 +24,14 @@ #pragma GCC diagnostic pop +namespace { + struct Options { Options() : host{"localhost:6544"} { } std::string host; }; - -namespace { - std::ostream& operator<<(std::ostream& ostream, const rslp::Command& command) { using Type = rslp::Command_Data::DataCase; @@ -80,8 +79,8 @@ Options parse_commandline(int argc, char** argv) bool show_help{}; auto cli = ( - clipp::option("-h", "--host").set(options.host) - .doc("Set the host (and port, optional) to connect to [default: localhost:6544]"), + clipp::option("-h", "--host") & clipp::value("host", options.host) + .doc("Sets the host and port to connect to [default: localhost:6544]"), clipp::option("--help").set(show_help).doc("Show help and exit.") ); @@ -95,7 +94,7 @@ Options parse_commandline(int argc, char** argv) class GrpcResplyClient { public: - GrpcResplyClient(std::shared_ptr channel) : + explicit GrpcResplyClient(std::shared_ptr channel) : stub_{rslp::ProtoAdapter::NewStub(channel)} { } diff --git a/src/proto-cli.cc b/src/proto-cli.cc index 7fa1e72..dc0e0d8 100644 --- a/src/proto-cli.cc +++ b/src/proto-cli.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -20,26 +21,22 @@ using namespace google; +namespace { + struct Options { - Options() : host{"localhost"}, port{"6543"} { } + Options() : host{"localhost:6543"} { } std::string host; - std::string port; }; - -namespace { - Options parse_commandline(int argc, char** argv) { Options options; bool show_help{}; auto cli = ( - clipp::option("-h", "--host").set(options.host) - .doc("Set the host to connect to [default: localhost]"), - clipp::option("-p", "--port").set(options.port) - .doc("Set the port to connect to [default: 6543]"), + clipp::option("-h", "--host") & clipp::value("host", options.host) + .doc("Sets the host and port to connect to [default: localhost:6543]"), clipp::option("--help").set(show_help).doc("Show help and exit.") ); @@ -94,15 +91,21 @@ std::ostream& operator<<(std::ostream& ostream, const rslp::Command& command) class ProtobufResplyClient { public: - ProtobufResplyClient(const std::string& host, const std::string& port) : - host_{host}, port_{port}, socket_{io_context_} - { } + explicit ProtobufResplyClient(const std::string& host) : + socket_{io_context_} + { + std::stringstream stream{host}; + + std::getline(stream, host_, ':'); + std::getline(stream, port_); + } bool connect() { asio::error_code error_code; asio::ip::tcp::resolver resolver{io_context_}; + std::cout << "host=" << host_ << "; port=" << port_ << std::endl; auto results = resolver.resolve(host_, port_, error_code); if (error_code) { std::cerr << "Error while connecting: " << error_code.message() << std::endl; @@ -182,8 +185,8 @@ private: } private: - const std::string& host_; - const std::string& port_; + std::string host_; + std::string port_; asio::io_context io_context_; asio::ip::tcp::socket socket_; @@ -197,13 +200,13 @@ int main(int argc, char* argv[]) GOOGLE_PROTOBUF_VERIFY_VERSION; auto options{parse_commandline(argc, argv)}; - ProtobufResplyClient client{options.host, options.port}; + ProtobufResplyClient client{options.host}; if (!client.connect()) { return 1; } while (std::cin) { - std::cout << options.host << ':' << options.port << "> "; + std::cout << options.host << "> "; std::string line; std::getline(std::cin, line); diff --git a/src/proxy.cc b/src/proxy.cc index 6c0e6e0..33659bf 100644 --- a/src/proxy.cc +++ b/src/proxy.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "asio.hpp" @@ -25,6 +26,7 @@ #include "spdlog/spdlog.h" #include "nlohmann/json.hpp" #include "resply.h" +#include "optional.h" #include "rslp.pb.h" #include "grpc++/grpc++.h" #include "grpc/support/log.h" @@ -38,46 +40,60 @@ using json = nlohmann::json; using namespace google; -struct Options { - Options() : - config_path{".proxy-conf.json"}, daemonize{}, log_path{"proxy.log"}, - protobuf_port{6543}, grpc_port{"6544"}, remote_host{"localhost:6379"}, verbose{} { } - - std::string config_path; - bool daemonize; - std::string log_path; - unsigned short protobuf_port; - std::string grpc_port; - std::string remote_host; - bool verbose; -}; - - namespace { const std::string GLOBAL_LOGGER_NAME{"Proxy"}; void resply_result_to_rslp_data(rslp::Command_Data* data, const resply::Result& result); +struct Options { + bool daemonize; + std::string log_path; + unsigned short protobuf_port; + unsigned short grpc_port; + std::string redis_host; + bool verbose; +}; Options parse_commandline(int argc, char** argv) { - Options options; bool show_help{}, show_version{}; + Optional daemonize{}, verbose{}; + Optional protobuf_port{6543}, grpc_port{6544}; + Optional config_path{".proxy-conf.json"}; + Optional log_path{"proxy.log"}; + Optional redis_host{"localhost:6379"}; + auto cli = ( - clipp::option("-c", "--conf-path") & clipp::value("path", options.config_path) + clipp::option("-c", "--conf-path") & clipp::value("path") + .call([&](auto p) { config_path.set_value(p); }) .doc("Path to the configuration file [default: $CWD/.proxy-conf.json]"), - clipp::option("-d", "--daemonize").set(options.daemonize).doc("Fork to background."), - clipp::option("-l", "--log-path") & clipp::value("path", options.log_path) + + clipp::option("-d", "--daemonize") + .call([&](auto d) { daemonize.set_value(d); }) + .doc("Fork to background."), + + clipp::option("-l", "--log-path") & clipp::value("path") + .call([&](auto p) { log_path.set_value(p); }) .doc("Path to the log file [default: $CWD/proxy.log] (Only applies when daemonized.)"), - clipp::option("--protobuf-port") & clipp::value("port", options.protobuf_port) + + clipp::option("--protobuf-port") & clipp::integer("port") + .call([&](auto p) { protobuf_port.set_value(static_cast(std::stoi(p))); }) .doc("Port the protobuf server should listen on [default: 6543]"), - clipp::option("--grpc-port") & clipp::value("port", options.grpc_port) + + clipp::option("--grpc-port") & clipp::integer("port") + .call([&](auto p) { grpc_port.set_value(static_cast(std::stoi(p))); }) .doc("Port the gRPC server should listen on [default: 6544]"), - clipp::option("-r", "--remote-host") & clipp::value("host", options.remote_host) - .doc("Host (redis-server) to connect to [default: localhost:6379]"), - clipp::option("-v", "--verbose").set(options.verbose).doc("Enable verbose logging."), + + clipp::option("-r", "--redis-host") & clipp::value("host") + .call([&](auto h) { redis_host.set_value(h); }) + .doc("Host (redis server) to connect to [default: localhost:6379]"), + + clipp::option("-v", "--verbose") + .call([&](auto v) { verbose.set_value(v); }) + .doc("Enable verbose logging."), + clipp::option("--help").set(show_help).doc("Show help and exit."), clipp::option("--version").set(show_version).doc("Show version and exit.") ); @@ -96,6 +112,34 @@ Options parse_commandline(int argc, char** argv) std::exit(0); } + json config; + if (config_path.value_or_default().length()) { + std::ifstream file{config_path.value_or_default()}; + if (file) { + file >> config; + file.close(); + } + } + + auto choose_opt = [&](const std::string& name, const auto& opt) + -> typename std::remove_reference::type::value_type { + if (opt.has_value()) { + return opt.value(); + } else if (config.count(name)) { + return config[name]; + } + + return opt.default_value(); + }; + + Options options; + options.daemonize = choose_opt("daemonize", daemonize); + options.log_path = choose_opt("log-path", log_path); + options.protobuf_port = choose_opt("protobuf-port", protobuf_port); + options.grpc_port = choose_opt("grpc-port", grpc_port); + options.redis_host = choose_opt("redis-host", redis_host); + options.verbose = choose_opt("verbose", verbose); + return options; } @@ -363,7 +407,7 @@ public: for (;;) { auto server{std::make_shared( - options.remote_host, io_context + options.redis_host, io_context )}; acceptor.accept(server->socket()); @@ -378,7 +422,7 @@ private: class GrpcAdapter final : public rslp::ProtoAdapter::Service { public: - GrpcAdapter(const std::string& redis_host) : + explicit GrpcAdapter(const std::string& redis_host) : client_{redis_host}, logger_{create_logger(LOGGER_NAME)} { } @@ -473,9 +517,12 @@ public: setup_grpc_logging(); grpc::ServerBuilder builder; - builder.AddListeningPort("0.0.0.0:" + options.grpc_port, grpc::InsecureServerCredentials()); + builder.AddListeningPort( + "0.0.0.0:" + std::to_string(options.grpc_port), + grpc::InsecureServerCredentials() + ); - GrpcAdapter adapter{options.remote_host}; + GrpcAdapter adapter{options.redis_host}; adapter.initialize(); builder.RegisterService(&adapter); @@ -515,13 +562,11 @@ private: class Proxy { public: - Proxy(const Options& options) : + explicit Proxy(const Options& options) : options_{options}, logger_{spdlog::stdout_color_mt(LOGGER_NAME)} { } void run() { - read_config_file(); - if (options_.daemonize) { daemonize(); } @@ -541,22 +586,6 @@ public: } private: - void read_config_file() - { - if (!options_.config_path.length()) { - return; - } - - std::ifstream file{options_.config_path}; - - if (!file) { - logger_->warn("Configuration file not found! Using compiled-in defaults .."); - } else { - file >> config_; - file.close(); - } - } - void daemonize() { logger_->info("Daemonizing server, logfile: {}", options_.log_path); @@ -576,7 +605,6 @@ private: const Options& options_; std::shared_ptr logger_; - json config_; }; diff --git a/src/resply-cli.cc b/src/resply-cli.cc index 569b3a6..b16d4e4 100644 --- a/src/resply-cli.cc +++ b/src/resply-cli.cc @@ -16,22 +16,21 @@ #include "resply.h" +namespace { + struct Options { Options() : host{"localhost:6379"} { } std::string host; }; - -namespace { - Options parse_commandline(int argc, char** argv) { Options options; bool show_help{}, show_version{}; auto cli = ( - clipp::option("-h", "--host").set(options.host) + clipp::option("-h", "--host") & clipp::value("host", options.host) .doc("Set the host (and port, optional) to connect to [default: localhost:6379]"), clipp::option("--help").set(show_help).doc("Show help and exit."), clipp::option("--version").set(show_version).doc("Show version and exit.")