Unverified Commit 2ee9e994 authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #1101

Fix CLI parsing and ref syncing
parents 9e801235 0babb782
...@@ -32,13 +32,13 @@ IndentWidth: 2 ...@@ -32,13 +32,13 @@ IndentWidth: 2
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp Language: Cpp
MacroBlockBegin: "^BEGIN_STATE$" MacroBlockBegin: "^BEGIN_STATE$|CAF_BEGIN_TYPE_ID_BLOCK"
MacroBlockEnd: "^END_STATE$" MacroBlockEnd: "^END_STATE$|CAF_END_TYPE_ID_BLOCK"
MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 1
NamespaceIndentation: None NamespaceIndentation: None
PenaltyBreakAssignment: 10 PenaltyBreakAssignment: 25
PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakBeforeFirstCallParameter: 30
PenaltyReturnTypeOnItsOwnLine: 5 PenaltyReturnTypeOnItsOwnLine: 25
PointerAlignment: Left PointerAlignment: Left
ReflowComments: true ReflowComments: true
SortIncludes: true SortIncludes: true
......
...@@ -165,6 +165,11 @@ public: ...@@ -165,6 +165,11 @@ public:
/// Generates human-readable help text for all options. /// Generates human-readable help text for all options.
std::string help_text(bool global_only = true) const; std::string help_text(bool global_only = true) const;
/// Drops all options.
void clear() {
opts_.clear();
}
// -- parsing ---------------------------------------------------------------- // -- parsing ----------------------------------------------------------------
/// Parses a given range as CLI arguments into `config`. /// Parses a given range as CLI arguments into `config`.
......
...@@ -216,6 +216,22 @@ private: ...@@ -216,6 +216,22 @@ private:
variant_type data_; variant_type data_;
}; };
// -- convenience constants ----------------------------------------------------
/// Type of the `top_level_cli_parsing` constant.
using top_level_cli_parsing_t = std::false_type;
/// Signals parsing of top-level config values when used as third argument to
/// `parse_cli`.
constexpr auto top_level_cli_parsing = top_level_cli_parsing_t{};
/// Type of the `top_level_cli_parsing` constant.
using nested_cli_parsing_t = std::true_type;
/// Signals parsing of nested config values when used as third argument to
/// `parse_cli`.
constexpr auto nested_cli_parsing = nested_cli_parsing_t{};
// -- SumType-like access ------------------------------------------------------ // -- SumType-like access ------------------------------------------------------
template <class T> template <class T>
...@@ -236,7 +252,8 @@ struct default_config_value_access { ...@@ -236,7 +252,8 @@ struct default_config_value_access {
return x; return x;
} }
static void parse_cli(string_parser_state& ps, T& x) { template <class Nested>
static void parse_cli(string_parser_state& ps, T& x, Nested) {
detail::parse(ps, x); detail::parse(ps, x);
} }
}; };
...@@ -266,19 +283,36 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary, "dictionary"); ...@@ -266,19 +283,36 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary, "dictionary");
#undef CAF_DEFAULT_CONFIG_VALUE_ACCESS #undef CAF_DEFAULT_CONFIG_VALUE_ACCESS
template <> template <>
struct config_value_access<std::string> struct config_value_access<std::string> {
: default_config_value_access<std::string> {
using super = default_config_value_access<std::string>; using super = default_config_value_access<std::string>;
static std::string type_name() { static std::string type_name() {
return "string"; return "string";
} }
using super::parse_cli; static bool is(const config_value& x) {
return holds_alternative<std::string>(x.get_data());
}
static const std::string* get_if(const config_value* x) {
return caf::get_if<std::string>(&(x->get_data()));
}
static std::string get(const config_value& x) {
return caf::get<std::string>(x.get_data());
}
static std::string convert(std::string x) {
return x;
}
template <bool IsNested>
static void parse_cli(string_parser_state& ps, std::string& x, static void parse_cli(string_parser_state& ps, std::string& x,
const char* char_blacklist) { std::integral_constant<bool, IsNested>) {
detail::parse_element(ps, x, char_blacklist); if (IsNested)
detail::parse_element(ps, x, ",={}[]");
else
detail::parse(ps, x);
} }
}; };
...@@ -426,7 +460,8 @@ struct select_config_value_access<T, select_config_value_hint::is_integral> { ...@@ -426,7 +460,8 @@ struct select_config_value_access<T, select_config_value_hint::is_integral> {
return x; return x;
} }
static void parse_cli(string_parser_state& ps, T& x) { template <class Nested>
static void parse_cli(string_parser_state& ps, T& x, Nested) {
detail::parse(ps, x); detail::parse(ps, x);
} }
}; };
...@@ -486,23 +521,70 @@ struct select_config_value_access<T, select_config_value_hint::is_list> { ...@@ -486,23 +521,70 @@ struct select_config_value_access<T, select_config_value_hint::is_list> {
return result; return result;
} }
static void parse_cli(string_parser_state& ps, T& xs) { static void parse_cli(string_parser_state& ps, T& xs,
config_value::dictionary result; top_level_cli_parsing_t) {
bool has_open_token = ps.consume('['); bool has_open_token;
auto subtype = select_config_value_oracle<value_type>();
if (subtype == select_config_value_hint::is_list) {
// The outer square brackets are optional in nested lists. This means we
// need to check for "[[" at the beginning and otherwise we assume the
// leading '[' was omitted.
string_parser_state tmp{ps.i, ps.e};
has_open_token = tmp.consume('[') && tmp.consume('[');
if (has_open_token)
ps.consume('[');
} else {
has_open_token = ps.consume('[');
}
do { do {
if (has_open_token && ps.consume(']')) { ps.skip_whitespaces();
if (has_open_token) {
if (ps.consume(']')) {
ps.skip_whitespaces(); ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character; ps.code = ps.at_end() ? pec::success : pec::trailing_character;
return; return;
} }
} else if (ps.at_end()) {
// Allow trailing commas and empty strings.
ps.code = pec::success;
return;
}
value_type tmp; value_type tmp;
value_trait::parse_cli(ps, tmp); value_trait::parse_cli(ps, tmp, nested_cli_parsing);
if (ps.code > pec::trailing_character) if (ps.code > pec::trailing_character)
return; return;
xs.insert(xs.end(), std::move(tmp)); xs.insert(xs.end(), std::move(tmp));
} while (ps.consume(',')); } while (ps.consume(','));
if (has_open_token && !ps.consume(']')) if (has_open_token && !ps.consume(']')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character; ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
static void parse_cli(string_parser_state& ps, T& xs,
nested_cli_parsing_t) {
if (!ps.consume('[')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
do {
if (ps.consume(']')) {
ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
return;
}
value_type tmp;
value_trait::parse_cli(ps, tmp, nested_cli_parsing);
if (ps.code > pec::trailing_character)
return;
xs.insert(xs.end(), std::move(tmp));
} while (ps.consume(','));
if (!ps.consume(']')) {
ps.code = ps.at_end() ? pec::unexpected_eof : pec::unexpected_character;
return;
}
ps.skip_whitespaces(); ps.skip_whitespaces();
ps.code = ps.at_end() ? pec::success : pec::trailing_character; ps.code = ps.at_end() ? pec::success : pec::trailing_character;
} }
...@@ -559,7 +641,8 @@ struct select_config_value_access<T, select_config_value_hint::is_map> { ...@@ -559,7 +641,8 @@ struct select_config_value_access<T, select_config_value_hint::is_map> {
return std::move(*result); return std::move(*result);
} }
static void parse_cli(string_parser_state& ps, map_type& xs) { template <class Nested>
static void parse_cli(string_parser_state& ps, map_type& xs, Nested) {
detail::parse(ps, xs); detail::parse(ps, xs);
} }
...@@ -574,29 +657,30 @@ struct select_config_value_access<T, select_config_value_hint::is_map> { ...@@ -574,29 +657,30 @@ struct select_config_value_access<T, select_config_value_hint::is_map> {
template <> template <>
struct config_value_access<float> { struct config_value_access<float> {
static inline std::string type_name() { static std::string type_name() {
return "real32"; return "real32";
} }
static inline bool is(const config_value& x) { static bool is(const config_value& x) {
return holds_alternative<double>(x.get_data()); return holds_alternative<double>(x.get_data());
} }
static inline optional<float> get_if(const config_value* x) { static optional<float> get_if(const config_value* x) {
if (auto res = caf::get_if<double>(&(x->get_data()))) if (auto res = caf::get_if<double>(&(x->get_data())))
return static_cast<float>(*res); return static_cast<float>(*res);
return none; return none;
} }
static inline float get(const config_value& x) { static float get(const config_value& x) {
return static_cast<float>(caf::get<double>(x.get_data())); return static_cast<float>(caf::get<double>(x.get_data()));
} }
static inline double convert(float x) { static double convert(float x) {
return x; return x;
} }
static inline void parse_cli(string_parser_state& ps, float& x) { template <class Nested>
static void parse_cli(string_parser_state& ps, float& x, Nested) {
detail::parse(ps, x); detail::parse(ps, x);
} }
}; };
...@@ -648,7 +732,8 @@ struct config_value_access<std::tuple<Ts...>> { ...@@ -648,7 +732,8 @@ struct config_value_access<std::tuple<Ts...>> {
return result; return result;
} }
static void parse_cli(string_parser_state& ps, tuple_type& xs) { template <class Nested>
static void parse_cli(string_parser_state& ps, tuple_type& xs, Nested) {
rec_parse(ps, xs, detail::int_token<0>(), detail::type_list<Ts...>()); rec_parse(ps, xs, detail::int_token<0>(), detail::type_list<Ts...>());
} }
...@@ -727,7 +812,7 @@ private: ...@@ -727,7 +812,7 @@ private:
static void rec_parse(string_parser_state& ps, tuple_type& xs, static void rec_parse(string_parser_state& ps, tuple_type& xs,
detail::int_token<Pos>, detail::type_list<U, Us...>) { detail::int_token<Pos>, detail::type_list<U, Us...>) {
using trait = select_config_value_access_t<U>; using trait = select_config_value_access_t<U>;
trait::parse_cli(std::get<Pos>(xs)); trait::parse_cli(std::get<Pos>(xs), nested_cli_parsing);
if (ps.code > pec::trailing_character) if (ps.code > pec::trailing_character)
return; return;
if (sizeof...(Us) > 0 && !ps.consume(',')) if (sizeof...(Us) > 0 && !ps.consume(','))
......
...@@ -90,9 +90,10 @@ struct config_value_adaptor_access { ...@@ -90,9 +90,10 @@ struct config_value_adaptor_access {
return result; return result;
} }
static void parse_cli(string_parser_state& ps, value_type& x) { template <class Nested>
static void parse_cli(string_parser_state& ps, value_type& x, Nested nested) {
tuple_type tmp; tuple_type tmp;
tuple_access::parse_cli(ps, tmp); tuple_access::parse_cli(ps, tmp, nested);
if (ps.code <= pec::trailing_character) if (ps.code <= pec::trailing_character)
convert(tmp, x); convert(tmp, x);
} }
......
...@@ -57,7 +57,7 @@ public: ...@@ -57,7 +57,7 @@ public:
/// Parses the content for this field in `object` from `ps`. /// Parses the content for this field in `object` from `ps`.
virtual void parse_cli(string_parser_state& ps, Object& object, virtual void parse_cli(string_parser_state& ps, Object& object,
const char* char_blacklist = "") const = 0; bool is_nested) const = 0;
}; };
} // namespace caf } // namespace caf
...@@ -91,7 +91,8 @@ struct config_value_object_access { ...@@ -91,7 +91,8 @@ struct config_value_object_access {
return result; return result;
} }
static void parse_cli(string_parser_state& ps, object_type& x) { template <class Nested>
static void parse_cli(string_parser_state& ps, object_type& x, Nested) {
using field_type = config_value_field<object_type>; using field_type = config_value_field<object_type>;
std::vector<field_type*> parsed_fields; std::vector<field_type*> parsed_fields;
auto got = [&](field_type* f) { auto got = [&](field_type* f) {
...@@ -131,7 +132,7 @@ struct config_value_object_access { ...@@ -131,7 +132,7 @@ struct config_value_object_access {
return; return;
} }
std::string field_name; std::string field_name;
string_access::parse_cli(ps, field_name, "=}"); string_access::parse_cli(ps, field_name, nested_cli_parsing);
if (ps.code > pec::trailing_character) if (ps.code > pec::trailing_character)
return; return;
if (!ps.consume('=')) { if (!ps.consume('=')) {
...@@ -151,7 +152,7 @@ struct config_value_object_access { ...@@ -151,7 +152,7 @@ struct config_value_object_access {
ps.code = pec::repeated_field_name; ps.code = pec::repeated_field_name;
return; return;
} }
fptr->parse_cli(ps, x, ",}"); fptr->parse_cli(ps, x, true);
if (ps.code > pec::trailing_character) if (ps.code > pec::trailing_character)
return; return;
if (ps.at_end()) { if (ps.at_end()) {
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "caf/config_value_adaptor_field.hpp" #include "caf/config_value_adaptor_field.hpp"
#include "caf/config_value_field.hpp" #include "caf/config_value_field.hpp"
#include "caf/detail/config_value_field_base.hpp" #include "caf/detail/config_value_field_base.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
namespace caf { namespace caf {
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/config_value_field.hpp" #include "caf/config_value_field.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/optional.hpp" #include "caf/optional.hpp"
namespace caf { namespace caf {
...@@ -83,9 +82,13 @@ public: ...@@ -83,9 +82,13 @@ public:
} }
void parse_cli(string_parser_state& ps, object_type& x, void parse_cli(string_parser_state& ps, object_type& x,
const char* char_blacklist) const override { bool nested) const override {
using access = caf::select_config_value_access_t<value_type>;
value_type tmp; value_type tmp;
dispatch_parse_cli(ps, tmp, char_blacklist); if (nested)
access::parse_cli(ps, tmp, nested_cli_parsing);
else
access::parse_cli(ps, tmp, top_level_cli_parsing);
if (ps.code <= pec::trailing_character) { if (ps.code <= pec::trailing_character) {
if (predicate_ && !predicate_(tmp)) if (predicate_ && !predicate_(tmp))
ps.code = pec::illegal_argument; ps.code = pec::illegal_argument;
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/config_value_field.hpp" #include "caf/config_value_field.hpp"
#include "caf/detail/config_value_field_base.hpp" #include "caf/detail/config_value_field_base.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/detail/parse.hpp" #include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
#include "caf/optional.hpp" #include "caf/optional.hpp"
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 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 <type_traits>
#include "caf/config_value.hpp"
#include "caf/detail/type_traits.hpp"
namespace caf {
namespace detail {
template <class Trait>
struct dispatch_parse_cli_helper {
template <class... Ts>
auto operator()(Ts&&... xs)
-> decltype(Trait::parse_cli(std::forward<Ts>(xs)...)) {
return Trait::parse_cli(std::forward<Ts>(xs)...);
}
};
template <class Access, class T>
void dispatch_parse_cli(std::true_type, string_parser_state& ps, T& x,
const char* char_blacklist) {
Access::parse_cli(ps, x, char_blacklist);
}
template <class Access, class T>
void dispatch_parse_cli(std::false_type, string_parser_state& ps, T& x,
const char*) {
Access::parse_cli(ps, x);
}
template <class T>
void dispatch_parse_cli(string_parser_state& ps, T& x,
const char* char_blacklist) {
using access = caf::select_config_value_access_t<T>;
using helper_fun = dispatch_parse_cli_helper<access>;
using token_type = bool_token<detail::is_callable_with<
helper_fun, string_parser_state&, T&, const char*>::value>;
token_type token;
dispatch_parse_cli<access>(token, ps, x, char_blacklist);
}
} // namespace detail
} // namespace caf
...@@ -130,8 +130,9 @@ void read_uri(State& ps, Consumer&& consumer) { ...@@ -130,8 +130,9 @@ void read_uri(State& ps, Consumer&& consumer) {
return res; return res;
}; };
// Allowed character sets. // Allowed character sets.
auto path_char auto path_char = [](char c) {
= [](char c) { return uri_unprotected_char(c) || c == '/' || c == ':'; }; return uri_unprotected_char(c) || c == '/' || c == ':';
};
// Utility setters for avoiding code duplication. // Utility setters for avoiding code duplication.
auto set_path = [&] { consumer.path(take_str()); }; auto set_path = [&] { consumer.path(take_str()); };
auto set_host = [&] { consumer.host(take_str()); }; auto set_host = [&] { consumer.host(take_str()); };
......
...@@ -638,6 +638,7 @@ public: ...@@ -638,6 +638,7 @@ public:
CAF_HAS_MEMBER_TRAIT(size); CAF_HAS_MEMBER_TRAIT(size);
CAF_HAS_MEMBER_TRAIT(data); CAF_HAS_MEMBER_TRAIT(data);
CAF_HAS_MEMBER_TRAIT(clear);
/// Checks whether F is convertible to either `std::function<void (T&)>` /// Checks whether F is convertible to either `std::function<void (T&)>`
/// or `std::function<void (const T&)>`. /// or `std::function<void (const T&)>`.
......
...@@ -52,15 +52,28 @@ config_value get_impl(const void* ptr) { ...@@ -52,15 +52,28 @@ config_value get_impl(const void* ptr) {
return config_value{trait::convert(*reinterpret_cast<const T*>(ptr))}; return config_value{trait::convert(*reinterpret_cast<const T*>(ptr))};
} }
template <class T>
typename std::enable_if<detail::has_clear_member<T>::value>::type
clear_before_parsing(T& xs) {
xs.clear();
}
template <class T>
typename std::enable_if<!detail::has_clear_member<T>::value>::type
clear_before_parsing(T&) {
// nop
}
template <class T> template <class T>
expected<config_value> parse_impl(T* ptr, string_view str) { expected<config_value> parse_impl(T* ptr, string_view str) {
if (!ptr) { if (!ptr) {
T tmp; T tmp;
return parse_impl(&tmp, str); return parse_impl(&tmp, str);
} }
clear_before_parsing(*ptr);
using trait = select_config_value_access_t<T>; using trait = select_config_value_access_t<T>;
string_parser_state ps{str.begin(), str.end()}; string_parser_state ps{str.begin(), str.end()};
trait::parse_cli(ps, *ptr); trait::parse_cli(ps, *ptr, top_level_cli_parsing);
if (ps.code != pec::success) if (ps.code != pec::success)
return make_error(ps); return make_error(ps);
return config_value{trait::convert(*ptr)}; return config_value{trait::convert(*ptr)};
......
...@@ -161,6 +161,7 @@ auto config_option_set::parse(settings& config, argument_iterator first, ...@@ -161,6 +161,7 @@ auto config_option_set::parse(settings& config, argument_iterator first,
return static_cast<pec>(err.code()); return static_cast<pec>(err.code());
return pec::illegal_argument; return pec::illegal_argument;
} }
opt.store(*val);
entry[opt_name] = std::move(*val); entry[opt_name] = std::move(*val);
} }
return pec::success; return pec::success;
......
...@@ -38,6 +38,14 @@ namespace { ...@@ -38,6 +38,14 @@ namespace {
struct fixture { struct fixture {
config_option_set opts; config_option_set opts;
template <class T>
error read(settings& cfg, std::vector<std::string> args) {
auto res = opts.parse(cfg, std::move(args));
if (res.first != pec::success)
return res.first;
return none;
}
template <class T> template <class T>
expected<T> read(std::vector<std::string> args) { expected<T> read(std::vector<std::string> args) {
settings cfg; settings cfg;
...@@ -160,4 +168,78 @@ CAF_TEST(flat CLI parsing with nested categories) { ...@@ -160,4 +168,78 @@ CAF_TEST(flat CLI parsing with nested categories) {
CAF_CHECK_EQUAL(read<std::string>({"--foo.goo.bar=foobar"}), "foobar"); CAF_CHECK_EQUAL(read<std::string>({"--foo.goo.bar=foobar"}), "foobar");
} }
CAF_TEST(square brackets are optional on the command line) {
using int_list = std::vector<int>;
opts.add<int_list>("global", "value,v", "some list");
CAF_CHECK_EQUAL(read<int_list>({"--value=[1]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"--value=[1,]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"--value=[ 1 , ]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"--value=[1,2]"}), int_list({1, 2}));
CAF_CHECK_EQUAL(read<int_list>({"--value=[1, 2, 3]"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"--value=[1, 2, 3, ]"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"--value=1"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"--value=1,2,3"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"--value=1, 2 , 3 , "}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[1]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[1,]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[ 1 , ]"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[1,2]"}), int_list({1, 2}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[1, 2, 3]"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "[1, 2, 3, ]"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "1"}), int_list({1}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "1,2,3"}), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>({"-v", "1, 2 , 3 , "}), int_list({1, 2, 3}));
}
#define SUBTEST(msg) \
opts.clear(); \
if (true)
CAF_TEST(CLI arguments override defaults) {
using int_list = std::vector<int>;
using string_list = std::vector<std::string>;
SUBTEST("with ref syncing") {
settings cfg;
int_list ints;
string_list strings;
CAF_MESSAGE("add --foo and --bar options");
opts.add(strings, "global", "foo,f", "some list");
opts.add(ints, "global", "bar,b", "some list");
CAF_MESSAGE("test integer lists");
ints = int_list{1, 2, 3};
cfg["bar"] = config_value{ints};
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>(cfg, {"--bar=[10, 20, 30]"}), none);
CAF_CHECK_EQUAL(ints, int_list({10, 20, 30}));
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_MESSAGE("test string lists");
strings = string_list{"one", "two", "three"};
cfg["foo"] = config_value{strings};
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
string_list({"one", "two", "three"}));
CAF_CHECK_EQUAL(read<string_list>(cfg, {"--foo=[hello, world]"}), none);
CAF_CHECK_EQUAL(strings, string_list({"hello", "world"}));
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
string_list({"hello", "world"}));
}
SUBTEST("without ref syncing") {
settings cfg;
CAF_MESSAGE("add --foo and --bar options");
opts.add<string_list>("global", "foo,f", "some list");
opts.add<int_list>("global", "bar,b", "some list");
CAF_MESSAGE("test integer lists");
cfg["bar"] = config_value{int_list{1, 2, 3}};
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({1, 2, 3}));
CAF_CHECK_EQUAL(read<int_list>(cfg, {"--bar=[10, 20, 30]"}), none);
CAF_CHECK_EQUAL(get<int_list>(cfg, "bar"), int_list({10, 20, 30}));
CAF_MESSAGE("test string lists");
cfg["foo"] = config_value{string_list{"one", "two", "three"}};
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
string_list({"one", "two", "three"}));
CAF_CHECK_EQUAL(read<string_list>(cfg, {"--foo=[hello, world]"}), none);
CAF_CHECK_EQUAL(get<string_list>(cfg, "foo"),
string_list({"hello", "world"}));
}
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
* http://www.boost.org/LICENSE_1_0.txt. * * http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/ ******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE config_value #define CAF_SUITE config_value
#include "caf/test/unit_test.hpp"
#include "caf/config_value.hpp"
#include "caf/test/dsl.hpp"
#include <list> #include <list>
#include <map> #include <map>
...@@ -34,10 +35,11 @@ ...@@ -34,10 +35,11 @@
#include "caf/atom.hpp" #include "caf/atom.hpp"
#include "caf/deep_to_string.hpp" #include "caf/deep_to_string.hpp"
#include "caf/detail/bounds_checker.hpp" #include "caf/detail/bounds_checker.hpp"
#include "caf/make_config_option.hpp"
#include "caf/none.hpp" #include "caf/none.hpp"
#include "caf/pec.hpp" #include "caf/pec.hpp"
#include "caf/variant.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
#include "caf/variant.hpp"
using std::string; using std::string;
...@@ -255,6 +257,83 @@ CAF_TEST(successful parsing) { ...@@ -255,6 +257,83 @@ CAF_TEST(successful parsing) {
CAF_CHECK_EQUAL(get<di>(parse("{a=1,b=2}")), di({{"a", 1}, {"b", 2}})); CAF_CHECK_EQUAL(get<di>(parse("{a=1,b=2}")), di({{"a", 1}, {"b", 2}}));
} }
#define CHECK_CLI_PARSE(type, str, ...) \
do { \
/* Note: parse_impl from make_config_option.hpp internally dispatches */ \
/* to parse_cli. No need to replicate that wrapping code here. */ \
if (auto res = caf::detail::parse_impl<type>(nullptr, str)) { \
type expected_res{__VA_ARGS__}; \
if (auto unboxed = ::caf::get_if<type>(std::addressof(*res))) { \
if (*unboxed == expected_res) \
CAF_CHECK_PASSED("parse(" << str << ") == " << expected_res); \
else \
CAF_CHECK_FAILED(*unboxed << " != " << expected_res); \
} else { \
CAF_CHECK_FAILED(*res << " != " << expected_res); \
} \
} else { \
CAF_CHECK_FAILED("parse(" << str << ") -> " << res.error()); \
} \
} while (false)
#define CHECK_CLI_PARSE_FAILS(type, str) \
do { \
if (auto res = caf::detail::parse_impl<type>(nullptr, str)) { \
CAF_CHECK_FAILED("unexpected parser result: " << *res); \
} else { \
CAF_CHECK_PASSED("parse(" << str << ") == " << res.error()); \
} \
} while (false)
CAF_TEST(parsing via parse_cli enables shortcut syntax for some types) {
using ls = std::vector<string>; // List-of-strings.
using li = std::vector<int>; // List-of-integers.
using lli = std::vector<li>; // List-of-list-of-integers.
CAF_MESSAGE("lists can omit square brackets");
CHECK_CLI_PARSE(int, "123", 123);
CHECK_CLI_PARSE(li, "[ 1,2 , 3 ,]", 1, 2, 3);
CHECK_CLI_PARSE(li, "[ 1,2 , 3 ]", 1, 2, 3);
CHECK_CLI_PARSE(li, " 1,2 , 3 ,", 1, 2, 3);
CHECK_CLI_PARSE(li, " 1,2 , 3 ", 1, 2, 3);
CHECK_CLI_PARSE(li, " [ ] ", li{});
CHECK_CLI_PARSE(li, " ", li{});
CHECK_CLI_PARSE(li, "", li{});
CHECK_CLI_PARSE(li, "[123]", 123);
CHECK_CLI_PARSE(li, "123", 123);
CAF_MESSAGE("brackets must have matching opening/closing brackets");
CHECK_CLI_PARSE_FAILS(li, " 1,2 , 3 ,]");
CHECK_CLI_PARSE_FAILS(li, " 1,2 , 3 ]");
CHECK_CLI_PARSE_FAILS(li, "123]");
CHECK_CLI_PARSE_FAILS(li, "[ 1,2 , 3 ,");
CHECK_CLI_PARSE_FAILS(li, "[ 1,2 , 3 ");
CHECK_CLI_PARSE_FAILS(li, "[123");
CAF_MESSAGE("string lists can omit quotation marks");
CHECK_CLI_PARSE(string, R"_("123")_", "123");
CHECK_CLI_PARSE(string, R"_(123)_", "123");
CHECK_CLI_PARSE(ls, R"_([ "1 ","2" , "3" ,])_", "1 ", "2", "3");
CHECK_CLI_PARSE(ls, R"_([ 1,2 , 3 ,])_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_([ 1,2 , 3 ])_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( 1,2 , 3 ,)_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( 1,2 , 3 )_", "1", "2", "3");
CHECK_CLI_PARSE(ls, R"_( [ ] )_", ls{});
CHECK_CLI_PARSE(ls, R"_( )_", ls{});
CHECK_CLI_PARSE(ls, R"_(["abc"])_", "abc");
CHECK_CLI_PARSE(ls, R"_([abc])_", "abc");
CHECK_CLI_PARSE(ls, R"_("abc")_", "abc");
CHECK_CLI_PARSE(ls, R"_(abc)_", "abc");
CAF_MESSAGE("nested lists can omit the outer square brackets");
CHECK_CLI_PARSE(lli, "[[1, 2, 3, ], ]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[[1, 2, 3]]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[1, 2, 3, ]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[1, 2, 3]", li({1, 2, 3}));
CHECK_CLI_PARSE(lli, "[[1], [2]]", li({1}), li({2}));
CHECK_CLI_PARSE(lli, "[1], [2]", li({1}), li({2}));
CHECK_CLI_PARSE_FAILS(lli, "1");
CHECK_CLI_PARSE_FAILS(lli, "1, 2");
CHECK_CLI_PARSE_FAILS(lli, "[1, 2]]");
CHECK_CLI_PARSE_FAILS(lli, "[[1, 2]");
}
CAF_TEST(unsuccessful parsing) { CAF_TEST(unsuccessful parsing) {
auto parse = [](const string& str) { auto parse = [](const string& str) {
auto x = config_value::parse(str); auto x = config_value::parse(str);
......
...@@ -139,7 +139,7 @@ struct fixture { ...@@ -139,7 +139,7 @@ struct fixture {
CAF_CHECK(!foo_field.set(x, config_value(-1))); CAF_CHECK(!foo_field.set(x, config_value(-1)));
string_view input = "123"; string_view input = "123";
string_parser_state ps{input.begin(), input.end()}; string_parser_state ps{input.begin(), input.end()};
foo_field.parse_cli(ps, x); foo_field.parse_cli(ps, x, false);
CAF_CHECK_EQUAL(ps.code, pec::success); CAF_CHECK_EQUAL(ps.code, pec::success);
CAF_CHECK_EQUAL(foo_field.get(x), config_value(123)); CAF_CHECK_EQUAL(foo_field.get(x), config_value(123));
} }
......
...@@ -535,6 +535,28 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture; ...@@ -535,6 +535,28 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
::caf::test::engine::last_check_line(__LINE__); \ ::caf::test::engine::last_check_line(__LINE__); \
} while (false) } while (false)
#define CAF_CHECK_PASSED(msg) \
do { \
auto out = ::caf::test::logger::instance().massive(); \
out << term::green << "** " << term::blue << __FILE__ << term::yellow \
<< ":" << term::blue << __LINE__ \
<< ::caf::test::detail::fill(__LINE__) << term::reset << msg << '\n'; \
::caf::test::engine::current_test()->pass(); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while (false)
#define CAF_CHECK_FAILED(msg) \
do { \
auto out = ::caf::test::logger::instance().massive(); \
out << term::red << "!! " << term::blue << __FILE__ << term::yellow << ":" \
<< term::blue << __LINE__ << ::caf::test::detail::fill(__LINE__) \
<< term::reset << msg << '\n'; \
::caf::test::engine::current_test()->fail(false); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while (false)
#define CAF_CHECK(...) \ #define CAF_CHECK(...) \
do { \ do { \
static_cast<void>(::caf::test::detail::check( \ static_cast<void>(::caf::test::detail::check( \
......
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