Commit b1a512e2 authored by Dominik Charousset's avatar Dominik Charousset

Re-implement help texts

parent 64324ccf
......@@ -268,7 +268,10 @@ public:
bool cli_helptext_printed;
/// Stores CLI arguments that were not consumed by CAF.
message args_remainder;
message args_remainder CAF_DEPRECATED;
/// Stores CLI arguments that were not consumed by CAF.
string_list remainder;
// -- caf-run parameters -----------------------------------------------------
......@@ -430,8 +433,6 @@ private:
static std::string render_exit_reason(uint8_t, atom_value, const message&);
void extract_config_file_path(string_list& args);
config_option_set options_;
};
} // namespace caf
......
......@@ -96,6 +96,9 @@
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wnon-virtual-dtor\"")
# define CAF_PUSH_DEPRECATED_WARNING \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
# define CAF_POP_WARNINGS \
_Pragma("clang diagnostic pop")
# define CAF_ANNOTATE_FALLTHROUGH [[clang::fallthrough]]
......@@ -122,6 +125,9 @@
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
# define CAF_PUSH_DEPRECATED_WARNING \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
# define CAF_POP_WARNINGS \
_Pragma("GCC diagnostic pop")
# define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0)
......@@ -154,6 +160,8 @@
# define CAF_UNLIKELY(x) x
# define CAF_DEPRECATED
# define CAF_PUSH_WARNINGS
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING
# define CAF_PUSH_DEPRECATED_WARNING
# define CAF_POP_WARNINGS
# define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0)
#endif
......
......@@ -33,6 +33,7 @@ public:
struct vtbl_type {
error (*check)(const config_value&);
void (*store)(void*, const config_value&);
std::string (*type_name)();
};
// -- constructors, destructors, and assignment operators --------------------
......@@ -78,6 +79,9 @@ public:
/// @pre `check(x) == none`.
void store(const config_value& x) const;
/// Returns a human-readable representation of this option's expected type.
std::string type_name() const;
private:
std::string category_;
std::string long_name_;
......
......@@ -113,6 +113,9 @@ public:
return *this;
}
/// Generates human-readable help text for all options.
std::string help_text(bool global_only = true) const;
// -- parsing ----------------------------------------------------------------
/// Parses a given range as CLI arguments into `config`.
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "caf/fwd.hpp"
#include "caf/timestamp.hpp"
namespace caf {
namespace detail {
template <size_t Bytes>
struct type_name_builder_int_size;
template <>
struct type_name_builder_int_size<1> {
inline void operator()(std::string& result) const {
result += "8";
}
};
template <>
struct type_name_builder_int_size<2> {
inline void operator()(std::string& result) const {
result += "16";
}
};
template <>
struct type_name_builder_int_size<4> {
inline void operator()(std::string& result) const {
result += "32";
}
};
template <>
struct type_name_builder_int_size<8> {
inline void operator()(std::string& result) const {
result += "64";
}
};
template <class T, bool IsInteger = std::is_integral<T>::value>
struct type_name_builder;
template <>
struct type_name_builder<bool, true> {
inline void operator()(std::string& result) const {
result += "boolean";
}
};
template <>
struct type_name_builder<float, false> {
inline void operator()(std::string& result) const {
result += "32-bit real";
}
};
template <>
struct type_name_builder<double, false> {
inline void operator()(std::string& result) const {
result += "64-bit real";
}
};
template <>
struct type_name_builder<timespan, false> {
inline void operator()(std::string& result) const {
result += "timespan";
}
};
template <>
struct type_name_builder<std::string, false> {
inline void operator()(std::string& result) const {
result += "string";
}
};
template <>
struct type_name_builder<atom_value, false> {
inline void operator()(std::string& result) const {
result += "atom";
}
};
template <class T>
struct type_name_builder<T, true> {
void operator()(std::string& result) const {
// TODO: replace with if constexpr when switching to C++17
if (!std::is_signed<T>::value)
result += 'u';
result += "int";
type_name_builder_int_size<sizeof(T)> g;
g(result);
}
};
template <class T>
struct type_name_builder<std::vector<T>, false> {
void operator()(std::string& result) const {
result += "list of ";
type_name_builder<T> g;
g(result);
}
};
template <class T>
struct type_name_builder<std::map<std::string, T>, false> {
void operator()(std::string& result) const {
result += "dictionary of ";
type_name_builder<T> g;
g(result);
}
};
template <class T>
std::string type_name() {
std::string result;
type_name_builder<T> f;
f(result);
return result;
}
} // namespace detail
} // namespace caf
......@@ -20,6 +20,7 @@
#include "caf/config_option.hpp"
#include "caf/config_value.hpp"
#include "caf/detail/type_name.hpp"
#include "caf/pec.hpp"
namespace caf {
......@@ -34,7 +35,10 @@ config_option make_config_option(const char* category, const char* name,
return none;
return make_error(pec::type_mismatch);
},
nullptr
nullptr,
[] {
return detail::type_name<T>();
}
};
return {category, name, description, std::is_same<T, bool>::value, vtbl};
}
......@@ -51,6 +55,9 @@ config_option make_config_option(T& storage, const char* category,
},
[](void* ptr, const config_value& x) {
*static_cast<T*>(ptr) = get<T>(x);
},
[] {
return detail::type_name<T>();
}
};
return {category, name, description, std::is_same<T, bool>::value,
......
......@@ -29,6 +29,8 @@
#include "caf/detail/parser/read_string.hpp"
#include "caf/message_builder.hpp"
CAF_PUSH_DEPRECATED_WARNING
namespace caf {
actor_system_config::opt_group::opt_group(config_option_set& xs,
......@@ -92,14 +94,19 @@ actor_system_config::actor_system_config()
middleman_cached_udp_buffers = 10;
middleman_max_pending_msgs = 10;
// fill our options vector for creating INI and CLI parsers
opt_group{options_, "streaming"}
opt_group{custom_options_, "global"}
.add<bool>(cli_helptext_printed, "help,h?", "print help and exit")
.add<bool>(cli_helptext_printed, "long-help",
"print all help options and exit")
.add<bool>("dump-config", "print configuration in INI format and exit");
opt_group{custom_options_, "streaming"}
.add(streaming_desired_batch_complexity_us, "desired-batch-complexity-us",
"sets the desired timespan for a single batch")
.add(streaming_max_batch_delay_us, "max-batch-delay-us",
"sets the maximum delay for sending underfull batches in microseconds")
.add(streaming_credit_round_interval_us, "credit-round-interval-us",
"sets the length of credit intervals in microseconds");
opt_group{options_, "scheduler"}
opt_group{custom_options_, "scheduler"}
.add(scheduler_policy, "policy",
"sets the scheduling policy to either 'stealing' (default) or 'sharing'")
.add(scheduler_max_threads, "max-threads",
......@@ -112,7 +119,7 @@ actor_system_config::actor_system_config()
"sets the rate in ms in which the profiler collects data")
.add(scheduler_profiling_output_file, "profiling-output-file",
"sets the output file for the profiler");
opt_group(options_, "work-stealing")
opt_group(custom_options_, "work-stealing")
.add(work_stealing_aggressive_poll_attempts, "aggressive-poll-attempts",
"sets the number of zero-sleep-interval polling attempts")
.add(work_stealing_aggressive_steal_interval, "aggressive-steal-interval",
......@@ -127,7 +134,7 @@ actor_system_config::actor_system_config()
"sets the frequency of steal attempts during relaxed polling")
.add(work_stealing_relaxed_sleep_duration_us, "relaxed-sleep-duration",
"sets the sleep interval between poll attempts during relaxed polling");
opt_group{options_, "logger"}
opt_group{custom_options_, "logger"}
.add(logger_file_name, "file-name",
"sets the filesystem path of the log file")
.add(logger_file_format, "file-format",
......@@ -146,7 +153,7 @@ actor_system_config::actor_system_config()
"deprecated (use file-name instead)")
.add(logger_component_filter, "filter",
"deprecated (use console-component-filter instead)");
opt_group{options_, "middleman"}
opt_group{custom_options_, "middleman"}
.add(middleman_network_backend, "network-backend",
"sets the network backend to either 'default' or 'asio' (if available)")
.add(middleman_app_identifier, "app-identifier",
......@@ -171,10 +178,10 @@ actor_system_config::actor_system_config()
.add(middleman_max_pending_msgs, "max-pending-messages",
"sets the max number of UDP pending messages due to ordering "
"(default: 10)");
opt_group(options_, "opencl")
opt_group(custom_options_, "opencl")
.add(opencl_device_ids, "device-ids",
"restricts which OpenCL devices are accessed by CAF");
opt_group(options_, "openssl")
opt_group(custom_options_, "openssl")
.add(openssl_certificate, "certificate",
"sets the path to the file containining the certificate for this node PEM format")
.add(openssl_key, "key",
......@@ -227,31 +234,100 @@ actor_system_config::make_help_text(const std::vector<message::cli_arg>& xs) {
actor_system_config& actor_system_config::parse(int argc, char** argv,
const char* ini_file_cstr) {
if (argc < 2)
return *this;
string_list args{argv + 1, argv + argc};
string_list args;
if (argc > 1)
args.assign(argv + 1, argv + argc);
return parse(std::move(args), ini_file_cstr);
}
actor_system_config& actor_system_config::parse(int argc, char** argv,
std::istream& ini) {
if (argc < 2)
return *this;
string_list args{argv + 1, argv + argc};
string_list args;
if (argc > 1)
args.assign(argv + 1, argv + argc);
return parse(std::move(args), ini);
}
namespace {
struct ini_iter {
std::istream* ini;
char ch;
explicit ini_iter(std::istream* istr) : ini(istr) {
ini->get(ch);
}
ini_iter() : ini(nullptr) {
// nop
}
ini_iter(const ini_iter&) = default;
ini_iter& operator=(const ini_iter&) = default;
inline char operator*() const {
return ch;
}
inline ini_iter& operator++() {
ini->get(ch);
return *this;
}
};
struct ini_sentinel { };
bool operator!=(ini_iter iter, ini_sentinel) {
return !iter.ini->fail();
}
} // namespace <anonymous>
actor_system_config& actor_system_config::parse(string_list args,
std::istream& ini) {
// (2) content of the INI file overrides hard-coded defaults
// Content of the INI file overrides hard-coded defaults.
if (ini.good()) {
detail::ini_consumer consumer{options_, content};
detail::parser::state<std::istream_iterator<char>> res;
res.i = std::istream_iterator<char>{ini};
detail::ini_consumer consumer{custom_options_, content};
detail::parser::state<ini_iter, ini_sentinel> res;
res.i = ini_iter{&ini};
detail::parser::read_ini(res, consumer);
if (res.i != res.e)
std::cerr << "*** error in config file [line " << res.line << " col "
<< res.column << "]: " << to_string(res.code) << std::endl;
}
// CLI options override the content of the INI file.
using std::make_move_iterator;
auto res = custom_options_.parse(content, args);
if (res.second != args.end()) {
if (res.first != pec::success) {
std::cerr << "error: at argument \"" << *res.second
<< "\": " << to_string(res.first) << std::endl;
cli_helptext_printed = true;
}
auto first = args.begin();
first += std::distance(args.cbegin(), res.second);
remainder.insert(remainder.end(), make_move_iterator(first),
make_move_iterator(args.end()));
args_remainder = message_builder{remainder.begin(), remainder.end()}
.move_to_message();
}
// Generate help text if needed.
if (cli_helptext_printed) {
bool long_help = get_or(content, "global.long-help", false);
std::cout << custom_options_.help_text(!long_help) << std::endl;
}
// Generate INI dump if needed.
if (!cli_helptext_printed && get_or(content, "global.dump-config", false)) {
for (auto& category : content) {
std::cout << '[' << category.first << "]\n";
for (auto& kvp : category.second)
if (kvp.first != "dump-config")
std::cout << kvp.first << '=' << to_string(kvp.second) << '\n';
}
std::cout << std::flush;
cli_helptext_printed = true;
}
// (3) CLI options override the content of the INI file
options_.parse(content, args);
return *this;
}
......@@ -298,7 +374,7 @@ actor_system_config::add_error_category(atom_value x, error_renderer y) {
actor_system_config& actor_system_config::set_impl(const char* name,
config_value value) {
auto opt = options_.qualified_name_lookup(name);
auto opt = custom_options_.qualified_name_lookup(name);
if (opt != nullptr && opt->check(value) == none) {
opt->store(value);
content[opt->category()][name] = std::move(value);
......@@ -368,3 +444,5 @@ content(const actor_system_config& cfg) {
}
} // namespace caf
CAF_POP_WARNINGS
......@@ -44,23 +44,6 @@ string get_short_names(const char* name) {
namespace caf {
const char* type_name_visitor_tbl[] {
"a boolean",
"a float",
"a double",
"a string",
"an atom_value",
"an 8-bit integer",
"an 8-bit unsigned integer",
"a 16-bit integer",
"a 16-bit unsigned integer",
"a 32-bit integer",
"a 32-bit unsigned integer",
"a 64-bit integer",
"a 64-bit unsigned integer",
"a duration"
};
config_option::config_option(string category, const char* name,
string description, bool is_flag,
const vtbl_type& vtbl, void* value)
......@@ -93,4 +76,9 @@ void config_option::store(const config_value& x) const {
}
}
std::string config_option::type_name() const {
CAF_ASSERT(vtbl_.type_name != nullptr);
return vtbl_.type_name();
}
} // namespace caf
......@@ -18,6 +18,9 @@
#include "caf/config_option_set.hpp"
#include <map>
#include <set>
#include "caf/config_option.hpp"
#include "caf/config_value.hpp"
#include "caf/detail/algorithms.hpp"
......@@ -25,12 +28,94 @@
using std::string;
namespace {
struct string_builder {
std::string result;
};
template <size_t N>
string_builder& operator<<(string_builder& builder, const char (&cstr)[N]) {
builder.result.append(cstr, N - 1);
return builder;
}
string_builder& operator<<(string_builder& builder, char c) {
builder.result += c;
return builder;
}
string_builder& operator<<(string_builder& builder, const std::string& str) {
builder.result += str;
return builder;
}
void insert(string_builder& builder, size_t count, char ch) {
builder.result.insert(builder.result.end(), count, ch);
}
} // namespace <anonymous>
namespace caf {
config_option_set::config_option_set() {
// nop
}
std::string config_option_set::help_text(bool global_only) const {
//<--- argument --------> <---- desciption ---->
// (-w|--write) <string> : output file
auto build_argument = [](const config_option& x) {
string_builder sb;
if (x.short_names().empty()) {
sb << " --";
if (x.category() != "global")
sb << x.category() << '.';
sb << x.long_name();
if (!x.is_flag())
sb << '=';
} else {
sb << " (";
for (auto c : x.short_names())
sb << '-' << c << '|';
sb << "--";
if (x.category() != "global")
sb << x.category() << '.';
sb << x.long_name() << ") ";
}
if (!x.is_flag())
sb << "<" << x.type_name() << '>';
return std::move(sb.result);
};
// Sort argument + description by category.
using pair = std::pair<std::string, option_pointer>;
std::set<std::string> categories;
std::multimap<std::string, pair> args;
size_t max_arg_size = 0;
for (auto& opt : opts_) {
if (!global_only || opt.category() == "global") {
auto arg = build_argument(opt);
max_arg_size = std::max(max_arg_size, arg.size());
categories.emplace(opt.category());
args.emplace(opt.category(), std::make_pair(std::move(arg), &opt));
}
}
// Build help text by iterating over all categories in the multimap.
string_builder builder;
for (auto& category : categories) {
auto rng = args.equal_range(category);
builder << category << " options:\n";
for (auto i = rng.first; i != rng.second; ++i) {
builder << i->second.first;
CAF_ASSERT(max_arg_size >= i->second.first.size());
insert(builder, max_arg_size - i->second.first.size(), ' ');
builder << " : " << i->second.second->description() << '\n';
}
builder << '\n';
}
return std::move(builder.result);
}
auto config_option_set::parse(config_map& config, argument_iterator first,
argument_iterator last) const
-> std::pair<pec, argument_iterator> {
......@@ -75,12 +160,17 @@ auto config_option_set::parse(config_map& config, argument_iterator first,
if (i->compare(0, 2, "--") == 0) {
// Long options use the syntax "--<name>=<value>" and consume only a
// single argument.
auto assign_op = i->find('=');//std::find(i->begin(), i->end(), '=');
auto name = i->substr(2, assign_op - 2);
static constexpr auto npos = std::string::npos;
auto assign_op = i->find('=');
auto name = assign_op == npos ? i->substr(2)
: i->substr(2, assign_op - 2);
auto opt = cli_long_name_lookup(name);
if (opt == nullptr)
return {pec::not_an_option, i};
auto code = consume(*opt, i->begin() + assign_op + 1, i->end());
auto code = consume(*opt,
assign_op == npos ? i->end()
: i->begin() + assign_op + 1,
i->end());
if (code != pec::success)
return {code, i};
++i;
......
......@@ -100,4 +100,18 @@ CAF_TEST(parse with ref syncing) {
CAF_CHECK_EQUAL(get<int>(cfg, "foo.i"), 42);
}
CAF_TEST(implicit global) {
opts.add<int>("global", "value").add<bool>("global", "help");
CAF_MESSAGE("test long option with argument");
std::map<std::string, config_value::dictionary> cfg;
auto res = opts.parse(cfg, {"--value=42"});
CAF_CHECK_EQUAL(res.first, pec::success);
CAF_CHECK_EQUAL(get_if<int>(&cfg, "global.value"), 42);
CAF_MESSAGE("test long option flag");
cfg.clear();
res = opts.parse(cfg, {"--help"});
CAF_CHECK_EQUAL(res.first, pec::success);
CAF_CHECK_EQUAL(get_or(cfg, "global.help", false), true);
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment