Commit b1a512e2 authored by Dominik Charousset's avatar Dominik Charousset

Re-implement help texts

parent 64324ccf
...@@ -268,7 +268,10 @@ public: ...@@ -268,7 +268,10 @@ public:
bool cli_helptext_printed; bool cli_helptext_printed;
/// Stores CLI arguments that were not consumed by CAF. /// 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 ----------------------------------------------------- // -- caf-run parameters -----------------------------------------------------
...@@ -430,8 +433,6 @@ private: ...@@ -430,8 +433,6 @@ private:
static std::string render_exit_reason(uint8_t, atom_value, const message&); static std::string render_exit_reason(uint8_t, atom_value, const message&);
void extract_config_file_path(string_list& args); void extract_config_file_path(string_list& args);
config_option_set options_;
}; };
} // namespace caf } // namespace caf
......
...@@ -96,6 +96,9 @@ ...@@ -96,6 +96,9 @@
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \ # define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \
_Pragma("clang diagnostic push") \ _Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wnon-virtual-dtor\"") _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 \ # define CAF_POP_WARNINGS \
_Pragma("clang diagnostic pop") _Pragma("clang diagnostic pop")
# define CAF_ANNOTATE_FALLTHROUGH [[clang::fallthrough]] # define CAF_ANNOTATE_FALLTHROUGH [[clang::fallthrough]]
...@@ -122,6 +125,9 @@ ...@@ -122,6 +125,9 @@
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \ # define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING \
_Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") _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 \ # define CAF_POP_WARNINGS \
_Pragma("GCC diagnostic pop") _Pragma("GCC diagnostic pop")
# define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0) # define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0)
...@@ -154,6 +160,8 @@ ...@@ -154,6 +160,8 @@
# define CAF_UNLIKELY(x) x # define CAF_UNLIKELY(x) x
# define CAF_DEPRECATED # define CAF_DEPRECATED
# define CAF_PUSH_WARNINGS # define CAF_PUSH_WARNINGS
# define CAF_PUSH_NON_VIRTUAL_DTOR_WARNING
# define CAF_PUSH_DEPRECATED_WARNING
# define CAF_POP_WARNINGS # define CAF_POP_WARNINGS
# define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0) # define CAF_ANNOTATE_FALLTHROUGH static_cast<void>(0)
#endif #endif
......
...@@ -33,6 +33,7 @@ public: ...@@ -33,6 +33,7 @@ public:
struct vtbl_type { struct vtbl_type {
error (*check)(const config_value&); error (*check)(const config_value&);
void (*store)(void*, const config_value&); void (*store)(void*, const config_value&);
std::string (*type_name)();
}; };
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
...@@ -78,6 +79,9 @@ public: ...@@ -78,6 +79,9 @@ public:
/// @pre `check(x) == none`. /// @pre `check(x) == none`.
void store(const config_value& x) const; void store(const config_value& x) const;
/// Returns a human-readable representation of this option's expected type.
std::string type_name() const;
private: private:
std::string category_; std::string category_;
std::string long_name_; std::string long_name_;
......
...@@ -113,6 +113,9 @@ public: ...@@ -113,6 +113,9 @@ public:
return *this; return *this;
} }
/// Generates human-readable help text for all options.
std::string help_text(bool global_only = true) const;
// -- parsing ---------------------------------------------------------------- // -- parsing ----------------------------------------------------------------
/// Parses a given range as CLI arguments into `config`. /// 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 @@ ...@@ -20,6 +20,7 @@
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/detail/type_name.hpp"
#include "caf/pec.hpp" #include "caf/pec.hpp"
namespace caf { namespace caf {
...@@ -34,7 +35,10 @@ config_option make_config_option(const char* category, const char* name, ...@@ -34,7 +35,10 @@ config_option make_config_option(const char* category, const char* name,
return none; return none;
return make_error(pec::type_mismatch); return make_error(pec::type_mismatch);
}, },
nullptr nullptr,
[] {
return detail::type_name<T>();
}
}; };
return {category, name, description, std::is_same<T, bool>::value, vtbl}; 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, ...@@ -51,6 +55,9 @@ config_option make_config_option(T& storage, const char* category,
}, },
[](void* ptr, const config_value& x) { [](void* ptr, const config_value& x) {
*static_cast<T*>(ptr) = get<T>(x); *static_cast<T*>(ptr) = get<T>(x);
},
[] {
return detail::type_name<T>();
} }
}; };
return {category, name, description, std::is_same<T, bool>::value, return {category, name, description, std::is_same<T, bool>::value,
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
#include "caf/detail/parser/read_string.hpp" #include "caf/detail/parser/read_string.hpp"
#include "caf/message_builder.hpp" #include "caf/message_builder.hpp"
CAF_PUSH_DEPRECATED_WARNING
namespace caf { namespace caf {
actor_system_config::opt_group::opt_group(config_option_set& xs, actor_system_config::opt_group::opt_group(config_option_set& xs,
...@@ -92,14 +94,19 @@ actor_system_config::actor_system_config() ...@@ -92,14 +94,19 @@ actor_system_config::actor_system_config()
middleman_cached_udp_buffers = 10; middleman_cached_udp_buffers = 10;
middleman_max_pending_msgs = 10; middleman_max_pending_msgs = 10;
// fill our options vector for creating INI and CLI parsers // 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", .add(streaming_desired_batch_complexity_us, "desired-batch-complexity-us",
"sets the desired timespan for a single batch") "sets the desired timespan for a single batch")
.add(streaming_max_batch_delay_us, "max-batch-delay-us", .add(streaming_max_batch_delay_us, "max-batch-delay-us",
"sets the maximum delay for sending underfull batches in microseconds") "sets the maximum delay for sending underfull batches in microseconds")
.add(streaming_credit_round_interval_us, "credit-round-interval-us", .add(streaming_credit_round_interval_us, "credit-round-interval-us",
"sets the length of credit intervals in microseconds"); "sets the length of credit intervals in microseconds");
opt_group{options_, "scheduler"} opt_group{custom_options_, "scheduler"}
.add(scheduler_policy, "policy", .add(scheduler_policy, "policy",
"sets the scheduling policy to either 'stealing' (default) or 'sharing'") "sets the scheduling policy to either 'stealing' (default) or 'sharing'")
.add(scheduler_max_threads, "max-threads", .add(scheduler_max_threads, "max-threads",
...@@ -112,7 +119,7 @@ actor_system_config::actor_system_config() ...@@ -112,7 +119,7 @@ actor_system_config::actor_system_config()
"sets the rate in ms in which the profiler collects data") "sets the rate in ms in which the profiler collects data")
.add(scheduler_profiling_output_file, "profiling-output-file", .add(scheduler_profiling_output_file, "profiling-output-file",
"sets the output file for the profiler"); "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", .add(work_stealing_aggressive_poll_attempts, "aggressive-poll-attempts",
"sets the number of zero-sleep-interval polling attempts") "sets the number of zero-sleep-interval polling attempts")
.add(work_stealing_aggressive_steal_interval, "aggressive-steal-interval", .add(work_stealing_aggressive_steal_interval, "aggressive-steal-interval",
...@@ -127,7 +134,7 @@ actor_system_config::actor_system_config() ...@@ -127,7 +134,7 @@ actor_system_config::actor_system_config()
"sets the frequency of steal attempts during relaxed polling") "sets the frequency of steal attempts during relaxed polling")
.add(work_stealing_relaxed_sleep_duration_us, "relaxed-sleep-duration", .add(work_stealing_relaxed_sleep_duration_us, "relaxed-sleep-duration",
"sets the sleep interval between poll attempts during relaxed polling"); "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", .add(logger_file_name, "file-name",
"sets the filesystem path of the log file") "sets the filesystem path of the log file")
.add(logger_file_format, "file-format", .add(logger_file_format, "file-format",
...@@ -146,7 +153,7 @@ actor_system_config::actor_system_config() ...@@ -146,7 +153,7 @@ actor_system_config::actor_system_config()
"deprecated (use file-name instead)") "deprecated (use file-name instead)")
.add(logger_component_filter, "filter", .add(logger_component_filter, "filter",
"deprecated (use console-component-filter instead)"); "deprecated (use console-component-filter instead)");
opt_group{options_, "middleman"} opt_group{custom_options_, "middleman"}
.add(middleman_network_backend, "network-backend", .add(middleman_network_backend, "network-backend",
"sets the network backend to either 'default' or 'asio' (if available)") "sets the network backend to either 'default' or 'asio' (if available)")
.add(middleman_app_identifier, "app-identifier", .add(middleman_app_identifier, "app-identifier",
...@@ -171,10 +178,10 @@ actor_system_config::actor_system_config() ...@@ -171,10 +178,10 @@ actor_system_config::actor_system_config()
.add(middleman_max_pending_msgs, "max-pending-messages", .add(middleman_max_pending_msgs, "max-pending-messages",
"sets the max number of UDP pending messages due to ordering " "sets the max number of UDP pending messages due to ordering "
"(default: 10)"); "(default: 10)");
opt_group(options_, "opencl") opt_group(custom_options_, "opencl")
.add(opencl_device_ids, "device-ids", .add(opencl_device_ids, "device-ids",
"restricts which OpenCL devices are accessed by CAF"); "restricts which OpenCL devices are accessed by CAF");
opt_group(options_, "openssl") opt_group(custom_options_, "openssl")
.add(openssl_certificate, "certificate", .add(openssl_certificate, "certificate",
"sets the path to the file containining the certificate for this node PEM format") "sets the path to the file containining the certificate for this node PEM format")
.add(openssl_key, "key", .add(openssl_key, "key",
...@@ -227,31 +234,100 @@ actor_system_config::make_help_text(const std::vector<message::cli_arg>& xs) { ...@@ -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, actor_system_config& actor_system_config::parse(int argc, char** argv,
const char* ini_file_cstr) { const char* ini_file_cstr) {
if (argc < 2) string_list args;
return *this; if (argc > 1)
string_list args{argv + 1, argv + argc}; args.assign(argv + 1, argv + argc);
return parse(std::move(args), ini_file_cstr); return parse(std::move(args), ini_file_cstr);
} }
actor_system_config& actor_system_config::parse(int argc, char** argv, actor_system_config& actor_system_config::parse(int argc, char** argv,
std::istream& ini) { std::istream& ini) {
if (argc < 2) string_list args;
return *this; if (argc > 1)
string_list args{argv + 1, argv + argc}; args.assign(argv + 1, argv + argc);
return parse(std::move(args), ini); 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, actor_system_config& actor_system_config::parse(string_list args,
std::istream& ini) { 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()) { if (ini.good()) {
detail::ini_consumer consumer{options_, content}; detail::ini_consumer consumer{custom_options_, content};
detail::parser::state<std::istream_iterator<char>> res; detail::parser::state<ini_iter, ini_sentinel> res;
res.i = std::istream_iterator<char>{ini}; res.i = ini_iter{&ini};
detail::parser::read_ini(res, consumer); 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; return *this;
} }
...@@ -298,7 +374,7 @@ actor_system_config::add_error_category(atom_value x, error_renderer y) { ...@@ -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, actor_system_config& actor_system_config::set_impl(const char* name,
config_value value) { 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) { if (opt != nullptr && opt->check(value) == none) {
opt->store(value); opt->store(value);
content[opt->category()][name] = std::move(value); content[opt->category()][name] = std::move(value);
...@@ -368,3 +444,5 @@ content(const actor_system_config& cfg) { ...@@ -368,3 +444,5 @@ content(const actor_system_config& cfg) {
} }
} // namespace caf } // namespace caf
CAF_POP_WARNINGS
...@@ -44,23 +44,6 @@ string get_short_names(const char* name) { ...@@ -44,23 +44,6 @@ string get_short_names(const char* name) {
namespace caf { 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, config_option::config_option(string category, const char* name,
string description, bool is_flag, string description, bool is_flag,
const vtbl_type& vtbl, void* value) const vtbl_type& vtbl, void* value)
...@@ -93,4 +76,9 @@ void config_option::store(const config_value& x) const { ...@@ -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 } // namespace caf
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
#include "caf/config_option_set.hpp" #include "caf/config_option_set.hpp"
#include <map>
#include <set>
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/detail/algorithms.hpp" #include "caf/detail/algorithms.hpp"
...@@ -25,12 +28,94 @@ ...@@ -25,12 +28,94 @@
using std::string; 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 { namespace caf {
config_option_set::config_option_set() { config_option_set::config_option_set() {
// nop // 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, auto config_option_set::parse(config_map& config, argument_iterator first,
argument_iterator last) const argument_iterator last) const
-> std::pair<pec, argument_iterator> { -> std::pair<pec, argument_iterator> {
...@@ -75,12 +160,17 @@ auto config_option_set::parse(config_map& config, argument_iterator first, ...@@ -75,12 +160,17 @@ auto config_option_set::parse(config_map& config, argument_iterator first,
if (i->compare(0, 2, "--") == 0) { if (i->compare(0, 2, "--") == 0) {
// Long options use the syntax "--<name>=<value>" and consume only a // Long options use the syntax "--<name>=<value>" and consume only a
// single argument. // single argument.
auto assign_op = i->find('=');//std::find(i->begin(), i->end(), '='); static constexpr auto npos = std::string::npos;
auto name = i->substr(2, assign_op - 2); 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); auto opt = cli_long_name_lookup(name);
if (opt == nullptr) if (opt == nullptr)
return {pec::not_an_option, i}; 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) if (code != pec::success)
return {code, i}; return {code, i};
++i; ++i;
......
...@@ -100,4 +100,18 @@ CAF_TEST(parse with ref syncing) { ...@@ -100,4 +100,18 @@ CAF_TEST(parse with ref syncing) {
CAF_CHECK_EQUAL(get<int>(cfg, "foo.i"), 42); 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() 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