Commit afbf30de authored by Dominik Charousset's avatar Dominik Charousset

Switch to new, cleaner config syntax

parent e3f7e64c
......@@ -50,6 +50,14 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- The `to_string` output for `error` now renders the error code enum by default.
This renders the member functions `actor_system::render` and
`actor_system_config::render` obsolete.
- The configuration format of CAF has come a long way since first starting to
allow user-defined configuration via `.ini` files. Rather than sticking with
the weird hybrid that evolved over the years, we are going to get rid of the
last pieces of INI syntax and go with the much cleaner, scoped syntax. CAF is
still picking up `caf-application.ini` with this release and uses the
deprecated INI parser when given any other file name that ends in `.ini`.
However, the next version of CAF is only going to recognize the new format.
The new default file name is `caf-application.conf`.
### Changed
......@@ -112,6 +120,9 @@ is based on [Keep a Changelog](https://keepachangelog.com).
example: `typed_actor<result<double>(double)>`. We have reimplemented the
metaprogramming facilities `racts_to<...>` and `replies_to<...>::with<...>`
as an alternative way of writing the function signature.
- All parsing functions in `actor_system_config` that take an input stream
exclusively use the new configuration syntax (please consult the manual for
details and examples for the configuration syntax).
### Removed
......
# This file shows all possible parameters with defaults. For some values, CAF
# computes a value at runtime if the configuration does not provide a value. For
# example, "scheduler.max-threads" has no hard-coded default and instead adjusts
# to the number of cores available.
# when using the default scheduler
scheduler {
# accepted alternative: "sharing"
policy="stealing"
# configures whether the scheduler generates profiling output
enable-profiling=false
# forces a fixed number of threads if set
# max-threads=... (detected at runtime)
# maximum number of messages actors can consume in one run (int64 max)
max-throughput=9223372036854775807
# measurement resolution in milliseconds (only if profiling is enabled)
profiling-resolution=100ms
# output file for profiler data (only if profiling is enabled)
profiling-output-file="/dev/null"
}
# when using "stealing" as scheduler policy
work-stealing {
# number of zero-sleep-interval polling attempts
aggressive-poll-attempts=100
# frequency of steal attempts during aggressive polling
aggressive-steal-interval=10
# number of moderately aggressive polling attempts
moderate-poll-attempts=500
# frequency of steal attempts during moderate polling
moderate-steal-interval=5
# sleep interval between poll attempts
moderate-sleep-duration=50us
# frequency of steal attempts during relaxed polling
relaxed-steal-interval=1
# sleep interval between poll attempts
relaxed-sleep-duration=10ms
}
# when loading io::middleman
middleman {
# configures whether MMs try to span a full mesh
enable-automatic-connections=false
# application identifier of this node, prevents connection to other CAF
# instances with different identifier
app-identifier=""
# maximum number of consecutive I/O reads per broker
max-consecutive-reads=50
# heartbeat message interval in ms (0 disables heartbeating)
heartbeat-interval=0ms
# configures whether the MM attaches its internal utility actors to the
# scheduler instead of dedicating individual threads (needed only for
# deterministic testing)
attach-utility-actors=false
# configures whether the MM starts a background thread for I/O activity,
# setting this to true allows fully deterministic execution in unit test and
# requires the user to trigger I/O manually
manual-multiplexing=false
# disables communication via TCP
disable-tcp=false
# enable communication via UDP
enable-udp=false
# configures how many background workers are spawned for deserialization,
# by default CAF uses 1-4 workers depending on the number of cores
# workers=... (detected at runtime)
}
# when compiling with logging enabled
logger {
# file name template for output log file files (empty string disables logging)
file-name="actor_log_[PID]_[TIMESTAMP]_[NODE].log"
# format for rendering individual log file entries
file-format="%r %c %p %a %t %C %M %F:%L %m%n"
# configures the minimum severity of messages that are written to the log file
# (quiet|error|warning|info|debug|trace)
file-verbosity="trace"
# mode for console log output generation (none|colored|uncolored)
console="none"
# format for printing individual log entries to the console
console-format="%m"
# configures the minimum severity of messages that are written to the console
# (quiet|error|warning|info|debug|trace)
console-verbosity="trace"
# excludes listed components from logging (list of strings)
component-blacklist=[]
}
; This file shows all possible parameters with defaults.
; Values enclosed in <> are detected at runtime unless defined by the user.
; when using the default scheduler
[scheduler]
; accepted alternative: 'sharing'
policy='stealing'
; configures whether the scheduler generates profiling output
enable-profiling=false
; forces a fixed number of threads if set
max-threads=<number of cores>
; maximum number of messages actors can consume in one run
max-throughput=<infinite>
; measurement resolution in milliseconds (only if profiling is enabled)
profiling-resolution=100ms
; output file for profiler data (only if profiling is enabled)
profiling-output-file="/dev/null"
; when using 'stealing' as scheduler policy
[work-stealing]
; number of zero-sleep-interval polling attempts
aggressive-poll-attempts=100
; frequency of steal attempts during aggressive polling
aggressive-steal-interval=10
; number of moderately aggressive polling attempts
moderate-poll-attempts=500
; frequency of steal attempts during moderate polling
moderate-steal-interval=5
; sleep interval between poll attempts
moderate-sleep-duration=50us
; frequency of steal attempts during relaxed polling
relaxed-steal-interval=1
; sleep interval between poll attempts
relaxed-sleep-duration=10ms
; when loading io::middleman
[middleman]
; configures whether MMs try to span a full mesh
enable-automatic-connections=false
; application identifier of this node, prevents connection to other CAF
; instances with different identifier
app-identifier=""
; maximum number of consecutive I/O reads per broker
max-consecutive-reads=50
; heartbeat message interval in ms (0 disables heartbeating)
heartbeat-interval=0ms
; configures whether the MM attaches its internal utility actors to the
; scheduler instead of dedicating individual threads (needed only for
; deterministic testing)
attach-utility-actors=false
; configures whether the MM starts a background thread for I/O activity,
; setting this to true allows fully deterministic execution in unit test and
; requires the user to trigger I/O manually
manual-multiplexing=false
; disables communication via TCP
disable-tcp=false
; enable communication via UDP
enable-udp=false
; configures how many background workers are spawned for deserialization,
; by default CAF uses 1-4 workers depending on the number of cores
workers=<min(3, number of cores / 4) + 1>
; when compiling with logging enabled
[logger]
; file name template for output log file files (empty string disables logging)
file-name="actor_log_[PID]_[TIMESTAMP]_[NODE].log"
; format for rendering individual log file entries
file-format="%r %c %p %a %t %C %M %F:%L %m%n"
; configures the minimum severity of messages that are written to the log file
; (quiet|error|warning|info|debug|trace)
file-verbosity='trace'
; mode for console log output generation (none|colored|uncolored)
console='none'
; format for printing individual log entries to the console
console-format="%m"
; configures the minimum severity of messages that are written to the console
; (quiet|error|warning|info|debug|trace)
console-verbosity='trace'
; excludes listed components from logging (list of atoms)
component-blacklist=[]
......@@ -79,6 +79,7 @@ add_library(libcaf_core_obj OBJECT ${CAF_CORE_HEADERS}
src/detail/behavior_impl.cpp
src/detail/behavior_stack.cpp
src/detail/blocking_behavior.cpp
src/detail/config_consumer.cpp
src/detail/get_mac_addresses.cpp
src/detail/get_process_id.cpp
src/detail/get_root_uuid.cpp
......@@ -227,12 +228,14 @@ caf_add_test_suites(caf-core-test
deep_to_string
detached_actors
detail.bounds_checker
detail.config_consumer
detail.ieee_754
detail.ini_consumer
detail.limited_vector
detail.meta_object
detail.parse
detail.parser.read_bool
detail.parser.read_config
detail.parser.read_floating_point
detail.parser.read_ini
detail.parser.read_number
......
......@@ -107,23 +107,23 @@ public:
// -- modifiers --------------------------------------------------------------
/// Parses `args` as tuple of strings containing CLI options and `ini_stream`
/// as INI formatted input stream.
error parse(string_list args, std::istream& ini);
/// Parses `args` as tuple of strings containing CLI options and `config` as
/// configuration file.
error parse(string_list args, std::istream& config);
/// Parses `args` as tuple of strings containing CLI options and tries to
/// open `ini_file_cstr` as INI formatted config file. The parsers tries to
/// open `caf-application.ini` if `ini_file_cstr` is `nullptr`.
error parse(string_list args, const char* ini_file_cstr = nullptr);
/// Parses `args` as tuple of strings containing CLI options and tries to open
/// `config_file_cstr` as config file. The parsers tries to open
/// `caf-application.conf` or `caf-application.ini` (deprecated) if
/// `config_file_cstr` is `nullptr`.
error parse(string_list args, const char* config_file_cstr = nullptr);
/// Parses the CLI options `{argc, argv}` and `ini_stream` as INI formatted
/// input stream.
error parse(int argc, char** argv, std::istream& ini);
/// Parses the CLI options `{argc, argv}` and `config` as configuration file.
error parse(int argc, char** argv, std::istream& config);
/// Parses the CLI options `{argc, argv}` and tries to open `ini_file_cstr`
/// as INI formatted config file. The parsers tries to open
/// `caf-application.ini` if `ini_file_cstr` is `nullptr`.
error parse(int argc, char** argv, const char* ini_file_cstr = nullptr);
/// Parses the CLI options `{argc, argv}` and tries to open `config_file_cstr`
/// as config file. The parsers tries to open `caf-application.conf` or
/// `caf-application.ini` (deprecated) if `config_file_cstr` is `nullptr`.
error parse(int argc, char** argv, const char* config_file_cstr = nullptr);
/// Allows other nodes to spawn actors created by `fun`
/// dynamically by using `name` as identifier.
......@@ -324,6 +324,10 @@ protected:
config_option_set custom_options_;
private:
// TODO: deprecated. Remove with CAF 0.19.
static error parse_ini(std::istream& source, const config_option_set& opts,
settings& result);
actor_system_config& set_impl(string_view name, config_value value);
error extract_config_file_path(string_list& args);
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 <string>
#include "caf/config_option_set.hpp"
#include "caf/config_value.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/dictionary.hpp"
#include "caf/settings.hpp"
namespace caf::detail {
class config_consumer;
class config_value_consumer;
/// Consumes a list of config values.
class CAF_CORE_EXPORT config_list_consumer {
public:
// -- constructors, destructors, and assignment operators --------------------
config_list_consumer(const config_option_set* options,
config_consumer* parent);
config_list_consumer(const config_option_set* options,
config_list_consumer* parent);
explicit config_list_consumer(config_value_consumer* parent);
config_list_consumer(config_list_consumer&&) = default;
config_list_consumer& operator=(config_list_consumer&&) = default;
// -- properties -------------------------------------------------------------
pec end_list();
config_list_consumer begin_list() {
return config_list_consumer{options_, this};
}
config_consumer begin_map();
template <class T>
void value(T&& x) {
xs_.emplace_back(std::forward<T>(x));
}
std::string qualified_key();
private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<config_consumer*, config_list_consumer*, config_value_consumer*>
parent_;
config_value::list xs_;
};
/// Consumes a series of key-vale pairs from an application configuration.
class CAF_CORE_EXPORT config_consumer {
public:
// -- constructors, destructors, and assignment operators --------------------
config_consumer(const config_option_set* options, config_consumer* parent);
config_consumer(const config_option_set* options,
config_list_consumer* parent);
config_consumer(const config_option_set& options, settings& cfg);
explicit config_consumer(settings& cfg);
config_consumer(config_consumer&& other);
config_consumer& operator=(config_consumer&& other);
~config_consumer();
// -- properties -------------------------------------------------------------
config_consumer begin_map() {
return config_consumer{options_, this};
}
void end_map();
config_list_consumer begin_list() {
return config_list_consumer{options_, this};
}
void key(std::string name) {
current_key_ = std::move(name);
}
template <class T>
pec value(T&& x) {
return value_impl(config_value{std::forward<T>(x)});
}
const std::string& current_key() {
return current_key_;
}
std::string qualified_key();
private:
void destroy();
pec value_impl(config_value&& x);
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<none_t, config_consumer*, config_list_consumer*> parent_;
settings* cfg_ = nullptr;
std::string current_key_;
std::string category_;
};
/// Consumes a single configuration value.
class CAF_CORE_EXPORT config_value_consumer {
public:
config_value result;
template <class T>
void value(T&& x) {
result = config_value{std::forward<T>(x)};
}
config_list_consumer begin_list();
config_consumer begin_map();
};
} // namespace caf::detail
......@@ -59,4 +59,6 @@ CAF_CORE_EXPORT extern const char decimal_chars[11];
CAF_CORE_EXPORT extern const char octal_chars[9];
CAF_CORE_EXPORT extern const char quote_marks[3];
} // namespace caf::detail::parser
......@@ -20,8 +20,21 @@
// DSL is supposed to clean up all defines made in this header via
// `include "caf/detail/parser/fsm_undef.hpp"` at the end.
#include <type_traits>
#include "caf/detail/pp.hpp"
#define CAF_FSM_EVAL_ACTION(action) \
auto action_impl = [&]() -> decltype(auto) { return action; }; \
if constexpr (std::is_same<pec, decltype(action_impl())>::value) { \
if (auto code = action_impl(); code != pec::success) { \
ps.code = code; \
return; \
} \
} else { \
action_impl(); \
}
#define CAF_FSM_EVAL_MISMATCH_EC \
if (mismatch_ec == caf::pec::unexpected_character) \
ps.code = ch != '\n' ? mismatch_ec : caf::pec::unexpected_newline; \
......@@ -107,7 +120,7 @@
#define CAF_TRANSITION_IMPL3(target, whitelist, action) \
if (::caf::detail::parser::in_whitelist(whitelist, ch)) { \
action; \
CAF_FSM_EVAL_ACTION(action) \
CAF_TRANSITION_IMPL1(target) \
}
......@@ -139,7 +152,7 @@
#define CAF_EPSILON_IMPL3(target, whitelist, action) \
if (::caf::detail::parser::in_whitelist(whitelist, ch)) { \
action; \
CAF_FSM_EVAL_ACTION(action) \
CAF_EPSILON_IMPL1(target) \
}
......@@ -167,7 +180,7 @@
#define CAF_FSM_TRANSITION_IMPL4(fsm_call, target, whitelist, action) \
if (::caf::detail::parser::in_whitelist(whitelist, ch)) { \
action; \
CAF_FSM_EVAL_ACTION(action) \
CAF_FSM_TRANSITION_IMPL2(fsm_call, target) \
}
......@@ -195,7 +208,7 @@
#define CAF_FSM_EPSILON_IMPL4(fsm_call, target, whitelist, action) \
if (::caf::detail::parser::in_whitelist(whitelist, ch)) { \
action; \
CAF_FSM_EVAL_ACTION(action) \
CAF_FSM_EPSILON_IMPL2(fsm_call, target) \
}
......
......@@ -18,6 +18,8 @@
// This header intentionally has no `#pragma once`. See fsm.hpp.
#undef CAF_FSM_EVAL_ACTION
#undef CAF_FSM_EVAL_MISMATCH_EC
#undef start
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 <ctype.h>
#include <stack>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/read_bool.hpp"
#include "caf/detail/parser/read_number_or_timespan.hpp"
#include "caf/detail/parser/read_string.hpp"
#include "caf/detail/parser/read_uri.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
#include "caf/uri_builder.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace caf::detail::parser {
// Example input:
//
// section1 {
// value1 = 123
// value2 = "string"
// subsection1 = {
// value3 = 1.23
// value4 = 4e20
// }
// }
// section2 {
// value5 = 'atom'
// value6 = [1, 'two', "three", {
// a = "b",
// b = "c",
// }]
// }
//
template <class State, class Consumer>
void read_config_comment(State& ps, Consumer&&) {
// clang-format off
start();
term_state(init) {
transition(done, '\n')
transition(init)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer, class InsideList = std::false_type>
void read_config_value(State& ps, Consumer&& consumer,
InsideList inside_list = {});
template <class State, class Consumer>
void read_config_list(State& ps, Consumer&& consumer) {
// clang-format off
start();
state(init) {
epsilon(before_value)
}
state(before_value) {
transition(before_value, " \t\n")
transition(done, ']', consumer.end_list())
fsm_epsilon(read_config_comment(ps, consumer), before_value, '#')
fsm_epsilon(read_config_value(ps, consumer, std::true_type{}), after_value)
}
state(after_value) {
transition(after_value, " \t\n")
transition(before_value, ',')
transition(done, ']', consumer.end_list())
fsm_epsilon(read_config_comment(ps, consumer), after_value, '#')
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <bool Nested = true, class State, class Consumer>
void read_config_map(State& ps, Consumer&& consumer) {
std::string key;
auto alnum_or_dash = [](char x) {
return isalnum(x) || x == '-' || x == '_';
};
// clang-format off
start();
term_state(init) {
epsilon(await_key_name)
}
state(await_key_name) {
transition(await_key_name, " \t\n")
fsm_epsilon(read_config_comment(ps, consumer), await_key_name, '#')
fsm_epsilon(read_string(ps, key), await_assignment, quote_marks)
transition(read_key_name, alnum_or_dash, key = ch)
transition_if(Nested, done, '}', consumer.end_map())
epsilon_if(!Nested, done)
}
// Reads a key of a "key=value" line.
state(read_key_name) {
transition(read_key_name, alnum_or_dash, key += ch)
epsilon(await_assignment)
}
// Reads the assignment operator in a "key=value" line.
state(await_assignment) {
transition(await_assignment, " \t")
transition(await_value, "=:", consumer.key(std::move(key)))
epsilon(await_value, '{', consumer.key(std::move(key)))
}
// Reads the value in a "key=value" line.
state(await_value) {
transition(await_value, " \t")
fsm_epsilon(read_config_value(ps, consumer), after_value)
}
// Waits for end-of-line after reading a value
unstable_state(after_value) {
transition(after_value, " \t")
transition(had_newline, "\n")
transition(await_key_name, ',')
transition_if(Nested, done, '}', consumer.end_map())
fsm_epsilon(read_config_comment(ps, consumer), had_newline, '#')
epsilon_if(!Nested, done)
epsilon(unexpected_end_of_input)
}
// Allows users to skip the ',' for separating key/value pairs
unstable_state(had_newline) {
transition(had_newline, " \t\n")
transition(await_key_name, ',')
transition_if(Nested, done, '}', consumer.end_map())
fsm_epsilon(read_config_comment(ps, consumer), had_newline, '#')
fsm_epsilon(read_string(ps, key), await_assignment, quote_marks)
epsilon(read_key_name, alnum_or_dash)
epsilon_if(!Nested, done)
epsilon(unexpected_end_of_input)
}
state(unexpected_end_of_input) {
// no transitions, only needed for the unstable states
}
term_state(done) {
//nop
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_config_uri(State& ps, Consumer&& consumer) {
uri_builder builder;
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consumer.value(builder.make());
});
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(before_uri, '<')
}
state(before_uri) {
transition(before_uri, " \t\n")
fsm_epsilon(read_uri(ps, builder), after_uri)
}
state(after_uri) {
transition(after_uri, " \t\n")
transition(done, '>')
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer, class InsideList>
void read_config_value(State& ps, Consumer&& consumer, InsideList inside_list) {
// clang-format off
start();
state(init) {
fsm_epsilon(read_string(ps, consumer), done, quote_marks)
fsm_epsilon(read_number(ps, consumer), done, '.')
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_number_or_timespan(ps, consumer, inside_list),
done, "0123456789+-")
fsm_epsilon(read_config_uri(ps, consumer), done, '<')
fsm_transition(read_config_list(ps, consumer.begin_list()), done, '[')
fsm_transition(read_config_map(ps, consumer.begin_map()), done, '{')
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_config(State& ps, Consumer&& consumer) {
// clang-format off
start();
// Checks whether there's a top-level '{'.
term_state(init) {
transition(init, " \t\n")
fsm_epsilon(read_config_comment(ps, consumer), init, '#')
fsm_transition(read_config_map<false>(ps, consumer),
await_closing_brance, '{')
fsm_epsilon(read_config_map<false>(ps, consumer), init)
}
state(await_closing_brance) {
transition(await_closing_brance, " \t\n")
fsm_epsilon(read_config_comment(ps, consumer), await_closing_brance, '#')
transition(done, '}')
}
term_state(done) {
transition(done, " \t\n")
fsm_epsilon(read_config_comment(ps, consumer), done, '#')
}
fin();
// clang-format on
}
} // namespace caf::detail::parser
#include "caf/detail/parser/fsm_undef.hpp"
CAF_POP_WARNINGS
......@@ -188,7 +188,7 @@ void read_ini_value(State& ps, Consumer&& consumer, InsideList inside_list) {
// clang-format off
start();
state(init) {
fsm_epsilon(read_string(ps, consumer), done, '"')
fsm_epsilon(read_string(ps, consumer), done, quote_marks)
fsm_epsilon(read_number(ps, consumer), done, '.')
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_number_or_timespan(ps, consumer, inside_list),
......
......@@ -36,21 +36,40 @@ namespace caf::detail::parser {
/// unquoted strings may only include alphanumeric characters.
template <class State, class Consumer>
void read_string(State& ps, Consumer&& consumer) {
std::string res;
// Allow Consumer to be a string&, in which case we simply store the result
// directly in the reference.
using res_type
= std::conditional_t<std::is_same<Consumer, std::string&>::value,
std::string&, std::string>;
auto init_res = [](auto& c) -> res_type {
if constexpr (std::is_same<res_type, std::string&>::value) {
c.clear();
return c;
} else {
return std::string{};
}
};
res_type res = init_res(consumer);
auto g = caf::detail::make_scope_guard([&] {
if constexpr (std::is_same<res_type, std::string>::value)
if (ps.code <= pec::trailing_character)
consumer.value(std::move(res));
});
static constexpr char single_quote = '\'';
static constexpr char double_quote = '"';
char quote_mark = '\0';
// clang-format off
start();
state(init) {
transition(init, " \t")
transition(read_chars, '"')
transition(read_chars, double_quote, quote_mark = double_quote)
transition(read_chars, single_quote, quote_mark = single_quote)
transition(read_unquoted_chars, alphanumeric_chars, res += ch)
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"')
transition_if(quote_mark == double_quote, done, double_quote)
transition_if(quote_mark == single_quote, done, single_quote)
error_transition(pec::unexpected_newline, '\n')
transition(read_chars, any_char, res += ch)
}
......@@ -59,7 +78,10 @@ void read_string(State& ps, Consumer&& consumer) {
transition(read_chars, 'r', res += '\r')
transition(read_chars, 't', res += '\t')
transition(read_chars, '\\', res += '\\')
transition(read_chars, '"', res += '"')
transition_if(quote_mark == double_quote, read_chars, double_quote,
res += double_quote)
transition_if(quote_mark == single_quote, read_chars, single_quote,
res += single_quote)
error_transition(pec::invalid_escape_sequence)
}
term_state(read_unquoted_chars) {
......
......@@ -75,6 +75,9 @@ enum class pec : uint8_t {
missing_field = 20,
/// Parsing a range statement ('n..m' or 'n..m..step') failed.
invalid_range_expression,
/// Stopped after running into an invalid parser state. Should never happen
/// and most likely indicates a bug in the implementation.
invalid_state,
};
CAF_CORE_EXPORT std::string to_string(pec);
......
......@@ -97,6 +97,11 @@ public:
// nop
}
constexpr string_view(iterator first, iterator last) noexcept
: data_(first), size_(static_cast<size_t>(last - first)) {
// nop
}
#ifdef CAF_GCC
constexpr string_view(const char* cstr) noexcept
: data_(cstr), size_(strlen(cstr)) {
......
......@@ -27,8 +27,9 @@
#include "caf/config_option.hpp"
#include "caf/config_option_adder.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/gcd.hpp"
#include "caf/detail/ini_consumer.hpp"
#include "caf/detail/parser/read_config.hpp"
#include "caf/detail/parser/read_ini.hpp"
#include "caf/detail/parser/read_string.hpp"
#include "caf/message_builder.hpp"
......@@ -40,7 +41,7 @@ namespace caf {
namespace {
constexpr const char* default_config_file = "caf-application.ini";
constexpr const char* default_config_file = "$DEFAULT";
} // namespace
......@@ -226,35 +227,35 @@ error actor_system_config::parse(int argc, char** argv, std::istream& ini) {
namespace {
struct ini_iter {
struct config_iter {
std::istream* ini;
char ch;
explicit ini_iter(std::istream* istr) : ini(istr) {
explicit config_iter(std::istream* istr) : ini(istr) {
ini->get(ch);
}
ini_iter() : ini(nullptr), ch('\0') {
config_iter() : ini(nullptr), ch('\0') {
// nop
}
ini_iter(const ini_iter&) = default;
config_iter(const config_iter&) = default;
ini_iter& operator=(const ini_iter&) = default;
config_iter& operator=(const config_iter&) = default;
inline char operator*() const {
return ch;
}
inline ini_iter& operator++() {
inline config_iter& operator++() {
ini->get(ch);
return *this;
}
};
struct ini_sentinel {};
struct config_sentinel {};
bool operator!=(ini_iter iter, ini_sentinel) {
bool operator!=(config_iter iter, config_sentinel) {
return !iter.ini->fail();
}
......@@ -299,17 +300,17 @@ void print(const config_value::dictionary& xs, indentation indent) {
} // namespace
error actor_system_config::parse(string_list args, std::istream& ini) {
// Content of the INI file overrides hard-coded defaults.
if (ini.good()) {
if (auto err = parse_config(ini, custom_options_, content))
error actor_system_config::parse(string_list args, std::istream& config) {
// Contents of the config file override hard-coded defaults.
if (config.good()) {
if (auto err = parse_config(config, custom_options_, content))
return err;
} else {
// Not finding an explicitly defined config file is an error.
if (auto fname = get_if<std::string>(&content, "config-file"))
return make_error(sec::cannot_open_file, *fname);
}
// CLI options override the content of the INI file.
// CLI options override the content of the config file.
using std::make_move_iterator;
auto res = custom_options_.parse(content, args);
if (res.second != args.end()) {
......@@ -337,15 +338,32 @@ error actor_system_config::parse(string_list args, std::istream& ini) {
return adjust_content();
}
error actor_system_config::parse(string_list args, const char* ini_file_cstr) {
error actor_system_config::parse(string_list args,
const char* config_file_cstr) {
// Override default config file name if set by user.
if (ini_file_cstr != nullptr)
config_file_path = ini_file_cstr;
if (config_file_cstr != nullptr)
config_file_path = config_file_cstr;
// CLI arguments always win.
if (auto err = extract_config_file_path(args))
return err;
std::ifstream ini{config_file_path};
return parse(std::move(args), ini);
if (config_file_path == "$DEFAULT") {
std::ifstream conf{"caf-application.conf"};
// TODO: The .ini parser is deprecated. Remove with CAF 0.19.
if (!conf.is_open()) {
conf.open("caf-application.ini");
if (conf.is_open()) {
// Consume the ini file here, because the parse() overloead for taking
// an istream assumes the new config format.
if (auto err = parse_ini(conf, custom_options_, content))
return err;
std::ifstream dummy;
return parse(std::move(args), dummy);
}
}
return parse(std::move(args), conf);
}
std::ifstream conf{config_file_path};
return parse(std::move(args), conf);
}
actor_system_config& actor_system_config::add_actor_factory(std::string name,
......@@ -400,6 +418,14 @@ actor_system_config::parse_config_file(const char* filename,
std::ifstream f{filename};
if (!f.is_open())
return make_error(sec::cannot_open_file, filename);
// TODO: The .ini parser is deprecated. Remove this block with CAF 0.19.
string_view fname{filename, strlen(filename)};
if (ends_with(fname, ".ini")) {
settings result;
if (auto err = parse_ini(f, opts, result))
return err;
return result;
}
return parse_config(f, opts);
}
......@@ -422,8 +448,21 @@ error actor_system_config::parse_config(std::istream& source,
settings& result) {
if (!source)
return make_error(sec::runtime_error, "source stream invalid");
detail::ini_consumer consumer{opts, result};
parser_state<ini_iter, ini_sentinel> res{ini_iter{&source}};
detail::config_consumer consumer{opts, result};
parser_state<config_iter, config_sentinel> res{config_iter{&source}};
detail::parser::read_config(res, consumer);
if (res.i != res.e)
return make_error(res.code, res.line, res.column);
return none;
}
error actor_system_config::parse_ini(std::istream& source,
const config_option_set& opts,
settings& result) {
if (!source)
return make_error(sec::runtime_error, "source stream invalid");
detail::config_consumer consumer{opts, result};
parser_state<config_iter, config_sentinel> res{config_iter{&source}};
detail::parser::read_ini(res, consumer);
if (res.i != res.e)
return make_error(res.code, res.line, res.column);
......
......@@ -276,7 +276,7 @@ config_option_set::qualified_name_lookup(string_view category,
config_option_set::option_pointer
config_option_set::qualified_name_lookup(string_view name) const {
auto sep = name.find('.');
auto sep = name.rfind('.');
if (sep == string::npos)
return nullptr;
return qualified_name_lookup(name.substr(0, sep), name.substr(sep + 1));
......
......@@ -22,8 +22,8 @@
#include <ostream>
#include "caf/deep_to_string.hpp"
#include "caf/detail/ini_consumer.hpp"
#include "caf/detail/parser/read_ini.hpp"
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/parser/read_config.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/expected.hpp"
#include "caf/parser_state.hpp"
......@@ -67,9 +67,9 @@ expected<config_value> config_value::parse(string_view::iterator first,
if (++i == last)
return make_error(pec::unexpected_eof);
// Dispatch to parser.
detail::ini_value_consumer f;
detail::config_value_consumer f;
string_parser_state res{i, last};
parser::read_ini_value(res, f);
parser::read_config_value(res, f);
if (res.code == pec::success)
return std::move(f.result);
// Assume an unescaped string unless the first character clearly indicates
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/overload.hpp"
#include "caf/pec.hpp"
namespace caf::detail {
// -- config_list_consumer -----------------------------------------------------
config_list_consumer::config_list_consumer(const config_option_set* options,
config_consumer* parent)
: options_(options), parent_(parent) {
// nop
}
config_list_consumer::config_list_consumer(const config_option_set* options,
config_list_consumer* parent)
: options_(options), parent_(parent) {
// nop
}
config_list_consumer::config_list_consumer(config_value_consumer* parent)
: parent_(parent) {
// nop
}
pec config_list_consumer::end_list() {
auto f = make_overload(
[this](config_consumer* ptr) {
return ptr->value(config_value{std::move(xs_)});
},
[this](auto* ptr) {
ptr->value(config_value{std::move(xs_)});
return pec::success;
});
return visit(f, parent_);
}
config_consumer config_list_consumer::begin_map() {
return config_consumer{options_, this};
}
std::string config_list_consumer::qualified_key() {
auto f = make_overload([](config_value_consumer*) { return std::string{}; },
[](auto* ptr) { return ptr->qualified_key(); });
return visit(f, parent_);
}
// -- config_consumer ----------------------------------------------------------
config_consumer::config_consumer(const config_option_set* options,
config_consumer* parent)
: options_(options),
parent_(parent),
cfg_(new settings),
category_(parent->qualified_key()) {
// nop
}
config_consumer::config_consumer(const config_option_set* options,
config_list_consumer* parent)
: options_(options),
parent_(parent),
cfg_(new settings),
category_(parent->qualified_key()) {
// nop
}
config_consumer::config_consumer(const config_option_set& options,
settings& cfg)
: options_(&options), cfg_(&cfg), category_("global") {
// nop
}
config_consumer::config_consumer(settings& cfg) : cfg_(&cfg) {
// nop
}
config_consumer::config_consumer(config_consumer&& other)
: options_(other.options_), parent_(other.parent_), cfg_(other.cfg_) {
other.parent_ = none;
}
config_consumer& config_consumer::operator=(config_consumer&& other) {
destroy();
options_ = other.options_;
parent_ = other.parent_;
cfg_ = other.cfg_;
other.parent_ = none;
return *this;
}
config_consumer::~config_consumer() {
destroy();
}
void config_consumer::destroy() {
if (!holds_alternative<none_t>(parent_))
delete cfg_;
}
void config_consumer::end_map() {
auto f = [this](auto ptr) {
if constexpr (std::is_same<none_t, decltype(ptr)>::value) {
// nop
} else {
ptr->value(std::move(*cfg_));
}
};
visit(f, parent_);
}
std::string config_consumer::qualified_key() {
if (category_.empty())
return current_key_;
auto result = category_;
result += '.';
result += current_key_;
return result;
}
namespace {
void merge_into_place(settings& src, settings& dst) {
for (auto& [key, value] : src) {
if (auto src_sub = get_if<config_value::dictionary>(&value)) {
auto i = dst.find(key);
if (i == dst.end() || !holds_alternative<settings>(i->second))
dst.insert_or_assign(key, std::move(value));
else
merge_into_place(*src_sub, get<settings>(i->second));
} else {
dst.insert_or_assign(key, std::move(value));
}
}
}
} // namespace
pec config_consumer::value_impl(config_value&& x) {
// See whether there's an config_option associated to this key and perform a
// type check if necessary.
const config_option* opt;
if (options_ == nullptr) {
opt = nullptr;
} else {
opt = options_->qualified_name_lookup(category_, current_key_);
if (opt && opt->check(x) != none)
return pec::type_mismatch;
}
// Insert / replace value in the map.
if (auto dict = get_if<settings>(&x)) {
// Merge values into the destination, because it can already contain any
// number of unrelated entries.
auto i = cfg_->find(current_key_);
if (i == cfg_->end() || !holds_alternative<settings>(i->second))
cfg_->insert_or_assign(current_key_, std::move(x));
else
merge_into_place(*dict, get<settings>(i->second));
} else {
cfg_->insert_or_assign(current_key_, std::move(x));
}
// Sync with config option if needed.
if (opt) {
if (auto i = cfg_->find(current_key_); i != cfg_->end())
opt->store(i->second);
else
return pec::invalid_state;
}
return pec::success;
}
// -- config_value_consumer ----------------------------------------------------
config_list_consumer config_value_consumer::begin_list() {
return config_list_consumer{this};
}
config_consumer config_value_consumer::begin_map() {
return config_consumer{result.as_dictionary()};
}
} // namespace caf::detail
......@@ -33,4 +33,6 @@ const char decimal_chars[11] = "0123456789";
const char octal_chars[9] = "01234567";
const char quote_marks[3] = "\"'";
} // namespace caf::detail::parser
......@@ -69,8 +69,8 @@ struct fixture {
void parse(const char* file_content, string_list args = {}) {
cfg.clear();
cfg.remainder.clear();
std::istringstream ini{file_content};
if (auto err = cfg.parse(std::move(args), ini))
std::istringstream conf{file_content};
if (auto err = cfg.parse(std::move(args), conf))
CAF_FAIL("parse() failed: " << err);
}
};
......@@ -80,7 +80,7 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(actor_system_config_tests, fixture)
CAF_TEST(parsing - without CLI arguments) {
auto text = "[foo]\nbar=\"hello\"";
auto text = "foo{\nbar=\"hello\"}";
options("?foo").add<std::string>("bar,b", "some string parameter");
parse(text);
CAF_CHECK(cfg.remainder.empty());
......@@ -88,7 +88,7 @@ CAF_TEST(parsing - without CLI arguments) {
}
CAF_TEST(parsing - without CLI cfg.remainder) {
auto text = "[foo]\nbar=\"hello\"";
auto text = "foo{\nbar=\"hello\"}";
options("?foo").add<std::string>("bar,b", "some string parameter");
CAF_MESSAGE("CLI long name");
parse(text, {"--foo.bar=test"});
......@@ -109,7 +109,7 @@ CAF_TEST(parsing - without CLI cfg.remainder) {
}
CAF_TEST(parsing - with CLI cfg.remainder) {
auto text = "[foo]\nbar=\"hello\"";
auto text = "foo{\nbar=\"hello\"}";
options("?foo").add<std::string>("bar,b", "some string parameter");
CAF_MESSAGE("valid cfg.remainder");
parse(text, {"-b", "test", "hello", "world"});
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#define CAF_SUITE detail.config_consumer
#include "caf/detail/config_consumer.hpp"
#include "caf/test/dsl.hpp"
#include "caf/detail/parser/read_config.hpp"
using std::string;
using namespace caf;
// List-of-strings.
using ls = std::vector<std::string>;
namespace {
constexpr const string_view test_config1 = R"(
is_server=true
port=4242
nodes=["sun", "venus", ]
logger{
file-name = "foobar.conf" # our file name
}
scheduler { # more settings
timing = 2us # using microsecond resolution
}
)";
constexpr const string_view test_config2 = R"(
is_server = true
logger = {
file-name = "foobar.conf"
}
port = 4242
scheduler : {
timing = 2us,
}
nodes = ["sun", "venus"]
)";
struct fixture {
config_option_set options;
settings config;
fixture() {
options.add<bool>("global", "is_server", "enables server mode")
.add<uint16_t>("global", "port", "sets local or remote port")
.add<ls>("global", "nodes", "list of remote nodes")
.add<string>("logger", "file-name", "log output file")
.add<int>("scheduler", "padding", "some integer")
.add<timespan>("scheduler", "timing", "some timespan");
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(config_consumer_tests, fixture)
CAF_TEST(config_consumer) {
string_view str = test_config1;
detail::config_consumer consumer{options, config};
string_parser_state res{str.begin(), str.end()};
detail::parser::read_config(res, consumer);
CAF_CHECK_EQUAL(res.code, pec::success);
CAF_CHECK_EQUAL(string_view(res.i, res.e), string_view());
CAF_CHECK_EQUAL(get<bool>(config, "is_server"), true);
CAF_CHECK_EQUAL(get<uint16_t>(config, "port"), 4242u);
CAF_CHECK_EQUAL(get<ls>(config, "nodes"), ls({"sun", "venus"}));
CAF_CHECK_EQUAL(get<string>(config, "logger.file-name"), "foobar.conf");
CAF_MESSAGE(config);
CAF_CHECK_EQUAL(get<timespan>(config, "scheduler.timing"), timespan(2000));
}
CAF_TEST(simplified syntax) {
CAF_MESSAGE("read test_config");
{
detail::config_consumer consumer{options, config};
string_parser_state res{test_config1.begin(), test_config1.end()};
detail::parser::read_config(res, consumer);
CAF_CHECK_EQUAL(res.code, pec::success);
}
settings config2;
CAF_MESSAGE("read test_config2");
{
detail::config_consumer consumer{options, config2};
string_parser_state res{test_config2.begin(), test_config2.end()};
detail::parser::read_config(res, consumer);
CAF_CHECK_EQUAL(res.code, pec::success);
}
CAF_CHECK_EQUAL(config, config2);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 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. *
******************************************************************************/
#define CAF_SUITE detail.parser.read_config
#include "caf/detail/parser/read_config.hpp"
#include "caf/test/dsl.hpp"
#include "caf/config_value.hpp"
#include "caf/parser_state.hpp"
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
using namespace caf;
namespace {
using log_type = std::vector<std::string>;
struct test_consumer {
log_type log;
test_consumer() = default;
test_consumer(const test_consumer&) = delete;
test_consumer& operator=(const test_consumer&) = delete;
test_consumer& begin_map() {
log.emplace_back("{");
return *this;
}
void end_map() {
log.emplace_back("}");
}
test_consumer& begin_list() {
log.emplace_back("[");
return *this;
}
void end_list() {
log.emplace_back("]");
}
void key(std::string name) {
add_entry("key: ", std::move(name));
}
template <class T>
void value(T x) {
config_value cv{std::move(x)};
std::string entry = "value (";
entry += cv.type_name();
entry += "): ";
entry += to_string(cv);
log.emplace_back(std::move(entry));
}
void add_entry(const char* prefix, std::string str) {
str.insert(0, prefix);
log.emplace_back(std::move(str));
}
};
struct fixture {
expected<log_type> parse(string_view str, bool expect_success = true) {
test_consumer f;
string_parser_state res{str.begin(), str.end()};
detail::parser::read_config(res, f);
if ((res.code == pec::success) != expect_success) {
CAF_MESSAGE("unexpected parser result state: " << res.code);
CAF_MESSAGE("input remainder: " << std::string(res.i, res.e));
}
return std::move(f.log);
}
};
template <class... Ts>
log_type make_log(Ts&&... xs) {
return log_type{std::forward<Ts>(xs)...};
}
// Tests basic functionality.
constexpr const string_view conf0 = R"(
"foo=bar" {
foo="bar"
}
1group {
1value=321
}
_foo {
_bar=11
}
logger {
"padding":10
file-name = "foobar.ini" # our file name
}
scheduler { # more settings
timing = 2us # using microsecond resolution
x_ =.123
some-bool=true
some-other-bool=false
}
some-list=[
# here we have some list entries
123,
1..3,
23 # twenty-three!
,2..4..2,
"abc", # some comment and a trailing comma
]
some-map:{
# here we have some list entries
entry1:123,
entry2=23 # twenty-three! btw, comma is not mandatory
entry3= "abc" , # some comment and a trailing comma
}
middleman {
preconnect=[<
tcp://localhost:8080
>,<udp://remotehost?trust=false>]
}
)";
constexpr const std::string_view conf1 = R"(
{
"foo" : {
"bar" : 1,
"baz" : 2
}
}
)";
// clang-format off
const auto conf0_log = make_log(
"key: foo=bar",
"{",
"key: foo",
"value (string): \"bar\"",
"}",
"key: 1group",
"{",
"key: 1value",
"value (integer): 321",
"}",
"key: _foo",
"{",
"key: _bar",
"value (integer): 11",
"}",
"key: logger",
"{",
"key: padding",
"value (integer): 10",
"key: file-name",
"value (string): \"foobar.ini\"",
"}",
"key: scheduler",
"{",
"key: timing",
"value (timespan): 2us",
"key: x_",
"value (real): " + deep_to_string(.123),
"key: some-bool",
"value (boolean): true",
"key: some-other-bool",
"value (boolean): false",
"}",
"key: some-list",
"[",
"value (integer): 123",
"value (integer): 1",
"value (integer): 2",
"value (integer): 3",
"value (integer): 23",
"value (integer): 2",
"value (integer): 4",
"value (string): \"abc\"",
"]",
"key: some-map",
"{",
"key: entry1",
"value (integer): 123",
"key: entry2",
"value (integer): 23",
"key: entry3",
"value (string): \"abc\"",
"}",
"key: middleman",
"{",
"key: preconnect",
"[",
"value (uri): tcp://localhost:8080",
"value (uri): udp://remotehost?trust=false",
"]",
"}"
);
const auto conf1_log = make_log(
"key: foo",
"{",
"key: bar",
"value (integer): 1",
"key: baz",
"value (integer): 2",
"}"
);
// clang-format on
} // namespace
CAF_TEST_FIXTURE_SCOPE(read_config_tests, fixture)
CAF_TEST(read_config feeds into a consumer) {
CAF_CHECK_EQUAL(parse(conf0), conf0_log);
CAF_CHECK_EQUAL(parse(conf1), conf1_log);
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -81,14 +81,6 @@ struct test_consumer {
}
};
struct ini_consumer {
using inner_map = std::map<std::string, config_value>;
using section_map = std::map<std::string, inner_map>;
section_map sections;
section_map::iterator current_section;
};
struct fixture {
expected<log_type> parse(string_view str, bool expect_success = true) {
test_consumer f;
......
......@@ -24,6 +24,7 @@
#include <string>
#include "caf/expected.hpp"
#include "caf/parser_state.hpp"
#include "caf/string_view.hpp"
#include "caf/variant.hpp"
......@@ -39,16 +40,14 @@ struct string_parser_consumer {
}
};
using res_t = variant<pec, std::string>;
struct string_parser {
res_t operator()(string_view str) {
expected<std::string> operator()(string_view str) {
string_parser_consumer f;
string_parser_state res{str.begin(), str.end()};
detail::parser::read_string(res, f);
if (res.code == pec::success)
return f.x;
return res.code;
return make_error(res.code, res.column, std::string{res.i, res.e});
}
};
......@@ -75,18 +74,33 @@ CAF_TEST(empty string) {
CAF_CHECK_EQUAL(p(R"("" )"), ""_s);
CAF_CHECK_EQUAL(p(R"( "" )"), ""_s);
CAF_CHECK_EQUAL(p("\t \"\" \t\t\t "), ""_s);
CAF_CHECK_EQUAL(p(R"('')"), ""_s);
CAF_CHECK_EQUAL(p(R"( '')"), ""_s);
CAF_CHECK_EQUAL(p(R"( '')"), ""_s);
CAF_CHECK_EQUAL(p(R"('' )"), ""_s);
CAF_CHECK_EQUAL(p(R"('' )"), ""_s);
CAF_CHECK_EQUAL(p(R"( '' )"), ""_s);
CAF_CHECK_EQUAL(p("\t '' \t\t\t "), ""_s);
}
CAF_TEST(non - empty quoted string) {
CAF_TEST(nonempty quoted string) {
CAF_CHECK_EQUAL(p(R"("abc")"), "abc"_s);
CAF_CHECK_EQUAL(p(R"("a b c")"), "a b c"_s);
CAF_CHECK_EQUAL(p(R"( "abcdefABCDEF" )"), "abcdefABCDEF"_s);
CAF_CHECK_EQUAL(p(R"('abc')"), "abc"_s);
CAF_CHECK_EQUAL(p(R"('a b c')"), "a b c"_s);
CAF_CHECK_EQUAL(p(R"( 'abcdefABCDEF' )"), "abcdefABCDEF"_s);
}
CAF_TEST(quoted string with escaped characters) {
CAF_CHECK_EQUAL(p(R"("a\tb\tc")"), "a\tb\tc"_s);
CAF_CHECK_EQUAL(p(R"("a\nb\r\nc")"), "a\nb\r\nc"_s);
CAF_CHECK_EQUAL(p(R"("a\\b")"), "a\\b"_s);
CAF_CHECK_EQUAL(p(R"("'hello' \"world\"")"), "'hello' \"world\""_s);
CAF_CHECK_EQUAL(p(R"('a\tb\tc')"), "a\tb\tc"_s);
CAF_CHECK_EQUAL(p(R"('a\nb\r\nc')"), "a\nb\r\nc"_s);
CAF_CHECK_EQUAL(p(R"('a\\b')"), "a\\b"_s);
CAF_CHECK_EQUAL(p(R"('\'hello\' "world"')"), "'hello' \"world\""_s);
}
CAF_TEST(unquoted strings) {
......@@ -97,8 +111,11 @@ CAF_TEST(unquoted strings) {
CAF_TEST(invalid strings) {
CAF_CHECK_EQUAL(p(R"("abc)"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p(R"('abc)"), pec::unexpected_eof);
CAF_CHECK_EQUAL(p("\"ab\nc\""), pec::unexpected_newline);
CAF_CHECK_EQUAL(p("'ab\nc'"), pec::unexpected_newline);
CAF_CHECK_EQUAL(p(R"("abc" def)"), pec::trailing_character);
CAF_CHECK_EQUAL(p(R"('abc' def)"), pec::trailing_character);
CAF_CHECK_EQUAL(p(R"( 123, )"), pec::trailing_character);
}
......
......@@ -24,6 +24,8 @@
#include <string>
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/parser/read_config.hpp"
#include "caf/none.hpp"
#include "caf/optional.hpp"
......@@ -204,4 +206,18 @@ CAF_TEST(custom type) {
CAF_CHECK_EQUAL(fb.bar, 24);
}
CAF_TEST(read_config accepts the to_string output of settings) {
fill();
auto str = to_string(x);
settings y;
config_option_set dummy;
detail::config_consumer consumer{dummy, y};
string_view str_view = str;
string_parser_state res{str_view.begin(), str_view.end()};
detail::parser::read_config(res, consumer);
CAF_CHECK(res.i == res.e);
CAF_CHECK_EQUAL(res.code, pec::success);
CAF_CHECK_EQUAL(x, y);
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -124,13 +124,13 @@ object *before* initializing an actor system with it.
.. _system-config-options:
Command Line Options and INI Configuration Files
------------------------------------------------
Command Line Options and Configuration Files
--------------------------------------------
CAF organizes program options in categories and parses CLI arguments as well as
INI files. CLI arguments override values in the INI file which override
hard-coded defaults. Users can add any number of custom program options by
implementing a subtype of ``actor_system_config``. The example below
configuration files. CLI arguments override values in the configuration file
which override hard-coded defaults. Users can add any number of custom program
options by implementing a subtype of ``actor_system_config``. The example below
adds three options to the ``global`` category.
.. literalinclude:: /examples/remoting/distributed_calculator.cpp
......@@ -166,37 +166,67 @@ CAF adds the program options ``help`` (with short names ``-h``
and ``-?``) as well as ``long-help`` to the ``global``
category.
The default name for the INI file is ``caf-application.ini``. Users can
change the file name and path by passing ``--config-file=<path>`` on the
The default name for the configuration file is ``caf-application.conf``. Users
can change the file name and path by passing ``--config-file=<path>`` on the
command line.
INI files are organized in categories. No value is allowed outside of a category
(no implicit ``global`` category). The parses uses the following syntax:
The syntax for the configuration files provides a clean JSON-like grammar that
is similar to commonly used configuration formats such as the file format of
``lighttpd``. In a nutshell, instead of writing:
+------------------------+-----------------------------+
.. code-block:: JSON
{
"my-category" : {
"first" : 1,
"second" : 2
}
}
you can reduce the noise by writing:
.. code-block:: none
my-category {
first = 1
second = 2
}
.. note::
CAF will accept both of the examples above and will produce the same result.
We recommend using the second style, mostly because it reduces syntax noise.
Unlike regular JSON, CAF's configuration format supports a couple of additional
syntax elements such as comments (comments start with ``#`` and end at the end
of the line) and, most notably, does *not* accept ``null``.
The parses uses the following syntax for writing key-value pairs:
+-------------------------+-----------------------------+
| ``key=true`` | is a boolean |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key=1`` | is an integer |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key=1.0`` | is an floating point number |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key=1ms`` | is an timespan |
+------------------------+-----------------------------+
| ``key='foo'`` | is an atom |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key='foo'`` | is a string |
+-------------------------+-----------------------------+
| ``key="foo"`` | is a string |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key=[0, 1, ...]`` | is as a list |
+------------------------+-----------------------------+
| ``key={a=1, b=2, ...}``| is a dictionary (map) |
+------------------------+-----------------------------+
+-------------------------+-----------------------------+
| ``key={a=1, b=2, ...}`` | is a dictionary (map) |
+-------------------------+-----------------------------+
The following example INI file lists all standard options in CAF and their
default value. Note that some options such as ``scheduler.max-threads``
The following example configuration file lists all standard options in CAF and
their default value. Note that some options such as ``scheduler.max-threads``
are usually detected at runtime and thus have no hard-coded default.
.. literalinclude:: /examples/caf-application.ini
:language: INI
.. literalinclude:: /examples/caf-application.conf
:language: none
.. _add-custom-message-type:
......
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