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().
This commit is contained in:
Christoph Heiss 2018-03-07 21:27:54 +01:00
parent c360cbe5fe
commit 0e7993c194
5 changed files with 195 additions and 74 deletions

92
include/optional.h Normal file
View file

@ -0,0 +1,92 @@
//
// Copyright 2018 Christoph Heiss <me@christoph-heiss.me>
// 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 <typename T>
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_;
};

View file

@ -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<grpc::Channel> channel) :
explicit GrpcResplyClient(std::shared_ptr<grpc::Channel> channel) :
stub_{rslp::ProtoAdapter::NewStub(channel)}
{ }

View file

@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include <ostream>
#include <sstream>
#include <arpa/inet.h>
#include <functional>
@ -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);

View file

@ -18,6 +18,7 @@
#include <cctype>
#include <algorithm>
#include <functional>
#include <type_traits>
#include <arpa/inet.h>
#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<bool> daemonize{}, verbose{};
Optional<unsigned short> protobuf_port{6543}, grpc_port{6544};
Optional<std::string> config_path{".proxy-conf.json"};
Optional<std::string> log_path{"proxy.log"};
Optional<std::string> 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<unsigned short>(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<unsigned short>(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<decltype(opt)>::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<ProtobufAdapter>(
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<spdlog::logger> logger_;
json config_;
};

View file

@ -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.")