Boost Program Options

Use Case

Boost Program Options library usage to access user input to the program

Session 1:

  • Updated main.cpp to use program options library
  • Captures user inputs

Libraries Used

Session 1

Video explanation of the code

C++-Boost-Program-Options

Program options input screen

root@efa32de8e392:/workspace/backend-cpp# LD_LIBRARY_PATH=/usr/src/redis-plus-plus-modules/lib:/workspace/backend-cpp/boost_1_81_0//stage/lib ./dist/backend-cpp --help
[2023-03-11 05:19:35.676734] [0x00007fe9039040c0] [info]    Allowed options:

Generic options:
  -v [ --version ]                      print version string
  --help                                produce help message
  -c [ --config ] arg (=app.cfg)        name of a file of a configuration.
  -s [ --sub-program-name ] arg (=ws-vendor)
                                        Module name to run. Options:
                                        ws-vendor|redis-uploader|webserver.

Configuration:
  --ws-vendor-hostname arg              websocket vendor hostname
  --ws-vendor-port arg                  websocket vendor port
  --ws-vendor-greeting-str arg          websocket vendor greeting string
  --ws-vendor-endpoint arg              websocket vendor endpoint
  --ws-vendor-no-threads arg            websocket vendor no of threads
  --redis-topics arg                    comma separated list of redis topics
  --webserver-hostname arg              webserver hostname or ip address to run
                                        on
  --webserver-port arg                  webserver port
  --webserver-root-dir arg              webserver root dir
  --webserver-no-threads arg            webserver no of threads
  --webserver-topics arg                comma separated list of topics
  --log-level arg                       log level

Source Code

ws-vendor-hostname=xxxx
ws-vendor-port=443
ws-vendor-greeting-str={"action": "subscribe", "symbols": "ETH-USD, BTC-USD"}
ws-vendor-endpoint=/ws/crypto
ws-vendor-no-threads=3
redis-topics=ETH-USD,BTC-USD
webserver-hostname=0.0.0.0
webserver-port=8080
webserver-root-dir=.
webserver-no-threads=5
webserver-topics=ETH-USD,BTC-USD
log-level=debug
view raw app.cfg hosted with ❤ by GitHub
/**
* SRAVZ LLC
**/
#include "main.hpp"
using namespace std;
int main(int ac, char* av[])
{
try {
int opt;
string config_file;
string sub_program_name;
string redis_host;
string redis_port;
string redis_password;
set<string> sub_program_options{ "ws-vendor", "redis-uploader", "webserver" };
set<string> log_levels{ "debug", "info", "warning", "error" };
// Declare a group of options that will be
// allowed only on command line
po::options_description generic("Generic options");
generic.add_options()
("version,v", "print version string")
("help", "produce help message")
("config,c", po::value<string>(&config_file)->default_value("app.cfg"),
"name of a file of a configuration.")
("sub-program-name,s", po::value<string>(&sub_program_name)->default_value("ws-vendor"),
"Module name to run. Options: ws-vendor|redis-uploader|webserver.")
;
// Declare a group of options that will be
// allowed both on command line and in
// config file
po::options_description config("Configuration");
config.add_options()
("ws-vendor-hostname",
po::value<string>(),
"websocket vendor hostname")
("ws-vendor-port",
po::value<string>(),
"websocket vendor port")
("ws-vendor-greeting-str",
po::value<string>(),
"websocket vendor greeting string")
("ws-vendor-endpoint",
po::value<string>(),
"websocket vendor endpoint")
("ws-vendor-no-threads",
po::value<int>(),
"websocket vendor no of threads")
("redis-topics",
po::value<string>(),
"comma separated list of redis topics")
("webserver-hostname",
po::value<string>(),
"webserver hostname or ip address to run on")
("webserver-port",
po::value<string>(),
"webserver port")
("webserver-root-dir",
po::value<string>(),
"webserver root dir")
("webserver-no-threads",
po::value<string>(),
"webserver no of threads")
("webserver-topics",
po::value<string>(),
"comma separated list of topics")
("log-level",
po::value<string>(),
"log level")
;
// Hidden options, will be allowed both on command line and
// in config file, but will not be shown to the user.
po::options_description hidden("Hidden options");
// hidden.add_options()
// ("input-file", po::value< vector<string> >(), "input file")
// ;
po::options_description cmdline_options;
cmdline_options.add(generic).add(config).add(hidden);
po::options_description config_file_options;
config_file_options.add(config).add(hidden);
po::options_description visible("Allowed options");
visible.add(generic).add(config);
po::positional_options_description p;
// p.add("input-file", -1);
// Calls to store, parse_command_line and notify functions
// cause vm to contain all the options found on the command line.
po::variables_map vm;
store(po::command_line_parser(ac, av).
options(cmdline_options).positional(p).run(), vm);
notify(vm);
ifstream ifs(config_file.c_str());
if (!ifs)
{
BOOST_LOG_TRIVIAL(info) << "can not open config file: " << config_file;
return 0;
}
else
{
store(parse_config_file(ifs, config_file_options), vm);
notify(vm);
}
// If variables map contains help. Please help
if (vm.count("help")) {
BOOST_LOG_TRIVIAL(info) << visible;
return 0;
}
if (vm.count("version")) {
BOOST_LOG_TRIVIAL(info) << "backend-cpp, version 1.0";
return 0;
}
// If variables map contains sub_program_name. Check it is a valid sub_program.
if (sub_program_options.find(sub_program_name) == sub_program_options.end()) {
BOOST_LOG_TRIVIAL(info) << "sub-program-name: " << sub_program_name << " invalid. should be one of: ";
for (string const& sub_program : sub_program_options)
{
BOOST_LOG_TRIVIAL(info) << sub_program << ' ';
}
return 1;
}
std::string hostname;
boost::program_options::options_description desc_env;
// TODO: Add these back if we need to pass the parameters at the cli
desc_env.add_options()("redis_host", boost::program_options::value<std::string>(&redis_host));
desc_env.add_options()("redis_port", boost::program_options::value<std::string>(&redis_port));
desc_env.add_options()("redis_password", boost::program_options::value<std::string>(&redis_password));
boost::program_options::variables_map vm_env;
boost::program_options::store(boost::program_options::parse_environment(desc_env,
[](const std::string& i_env_var)
{
switch(get_hash(i_env_var)) {
case "REDIS_HOST"_ :
{
return "redis_host";
break;
}
case "REDIS_PORT"_ :
return "redis_port";
break;
case "REDIS_PASSWORD"_ :
return "redis_password";
break;
default:
return "";
}
}),
vm_env);
boost::program_options::notify(vm_env);
// BOOST_LOG_TRIVIAL(info) << "Environment varaibles: \n";
// BOOST_LOG_TRIVIAL(info) << "redis_host: " << vm_env["redis_host"].as<string>() << '\n';
// BOOST_LOG_TRIVIAL(info) << "redis_port: " << vm_env["redis_port"].as<string>() << '\n';
// BOOST_LOG_TRIVIAL(info) << "redis_password: " << vm_env["redis_password"].as<string>() << '\n';
// Init program
switch(get_hash(vm["log-level"].as<string>())) {
case "debug"_ :
{
logging::core::get()->set_filter
(
logging::trivial::severity >= logging::trivial::debug
);
break;
}
case "info"_ :
logging::core::get()->set_filter
(
logging::trivial::severity >= logging::trivial::info
);
break;
case "warning"_ :
logging::core::get()->set_filter
(
logging::trivial::severity >= logging::trivial::warning
);
break;
case "error"_ :
logging::core::get()->set_filter
(
logging::trivial::severity >= logging::trivial::error
);
break;
default:
BOOST_LOG_TRIVIAL(info) << "Unsupported log level: " << vm["log-level"].as<string>() << endl;
return 1;
}
BOOST_LOG_TRIVIAL(info) << "sub_program_name";
switch(get_hash(sub_program_name)) {
case "ws-vendor"_ :
{
ws_vendor(vm["ws-vendor-hostname"].as<string>(),
vm["ws-vendor-port"].as<string>(),
vm["ws-vendor-greeting-str"].as<string>(),
vm["ws-vendor-endpoint"].as<string>(),
vm["ws-vendor-no-threads"].as<int>());
break;
}
case "redis-uploader"_ :
redis_uploader(vm["redis-topics"].as<string>());
break;
case "webserver"_ :
webserver(vm["webserver-hostname"].as<string>(),
vm["webserver-port"].as<string>(),
vm["webserver-root-dir"].as<string>(),
vm["webserver-no-threads"].as<string>(),
vm["webserver-topics"].as<string>());
break;
default:
BOOST_LOG_TRIVIAL(info) << "Unsupported option: " << sub_program_name << endl;
}
}
catch(exception& e)
{
BOOST_LOG_TRIVIAL(info) << e.what();
return 1;
}
return 0;
}
view raw main.cpp hosted with ❤ by GitHub
#pragma once
#include "util.hpp"
using namespace std;
// https://docs.sravz.com/docs/tech/cpp/boost-beast-websockets/
int ws_vendor(std::string host, std::string port, std::string text, std::string endpoint, int threads);
void redis_uploader(std::string topics);
// https://docs.sravz.com/docs/tech/cpp/boost-beast-websocket-server/
int webserver(std::string address_, std::string port_, std::string doc_root_, std::string threads_, std::string topics_);
// A helper function to simplify the main part.
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v)
{
copy(v.begin(), v.end(), ostream_iterator<T>(os, " "));
return os;
}
// https://gcc.godbolt.org/z/pfJXRm
// https://en.cppreference.com/w/cpp/language/consteval
// This function might be evaluated at compile-time, if the input
// is known at compile-time. Otherwise, it is executed at run-time.
constexpr inline
unsigned long long int get_hash(char const * str, int h = 0)
{
return (!str[h] ? 5381 : (get_hash(str, h+1)*33) ^ str[h] );
}
constexpr inline
unsigned long long int operator "" _(char const * p, size_t) { return get_hash(p); }
inline
unsigned long long int get_hash(std::string const & s) { return get_hash (s.c_str()); }
view raw main.hpp hosted with ❤ by GitHub
#ifndef SRAVZ_BACKENDCPP_UTIL_H
#define SRAVZ_BACKENDCPP_UTIL_H
// Includes
#include "boost/format.hpp"
#include "root_certificates.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp>
#include <boost/atomic.hpp>
#include <boost/beast.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/config.hpp>
#include <boost/coroutine2/all.hpp>
#include <boost/foreach.hpp>
#include <boost/json.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <boost/optional.hpp>
#include <boost/program_options.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/url.hpp>
#include <chrono>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <jwt/jwt.hpp>
#include <memory>
#include <mutex>
#include <redis-plus-plus/src/sw/redis++/errors.h>
#include <redis-plus-plus/test/src/sw/redis++/utils.h>
#include <set>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
// Namespaces
namespace attrs = boost::log::attributes;
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace expr = boost::log::expressions;
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace json = boost::json;
namespace keywords = boost::log::keywords;
namespace logging = boost::log;
namespace net = boost::asio; // from <boost/asio.hpp>
namespace po = boost::program_options;
namespace sinks = boost::log::sinks;
namespace src = boost::log::sources;
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
/* Using namespaces */
// using boost::format;
// using boost::io::group;
using RedisInstance = sw::redis::Redis;
using tcp = boost::asio::ip::tcp;
typedef boost::asio::io_context::executor_type executor_type;
typedef boost::asio::strand<executor_type> strand;
// Classes
class ThreadInfo
{
public:
beast::flat_buffer buffer;
std::shared_ptr<RedisInstance> redis;
ThreadInfo() {}
ThreadInfo(beast::flat_buffer buffer_, std::shared_ptr<RedisInstance> redis_) : buffer(buffer_), redis(redis_) {}
};
typedef std::map<boost::thread::id, ThreadInfo> ThreadsInfo;
// Functions
// Gets env variable
bool getenv(const char *name, std::string &env);
// Returns redis connection options to be used in redis connection creation
sw::redis::ConnectionOptions getRedisConnectionOptions();
#endif // end SRAVZ_BACKENDCPP_UTIL_H
view raw util.hpp hosted with ❤ by GitHub