Flow 1.0.0
Flow project: Full implementation reference.
option_set.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include "flow/cfg/cfg_fwd.hpp"
22#include "flow/log/log.hpp"
23#include "flow/error/error.hpp"
25#include <boost/algorithm/string.hpp>
26#include <boost/unordered_set.hpp>
27
28namespace flow::cfg
29{
30// Types.
31
32/// Un-templated base for Option_set.
34{
35public:
36 // Types.
37
38 /**
39 * Internal-use type to use with Option_set::Declare_options_func callback.
40 * The user of the class need not understand this nor use it directly.
41 *
42 * @internal
43 *
44 * The reason for the Option_set::Declare_options_func call, originally from FLOW_CFG_OPTION_SET_DECLARE_OPTION().
45 */
47 {
48 /**
49 * Internal use only through macro. Do not use directly.
50 *
51 * @internal
52 * Add option entries into Option_set::m_opts_for_parsing using Option_set::declare_option_for_parsing().
53 */
54 S_FILL_PARSING_ROLE_OPT_TABLE,
55
56 /**
57 * Internal use only through macro. Do not use directly.
58 *
59 * @internal
60 * Add option entries into Option_set::m_opts_for_help using Option_set::declare_option_for_help().
61 */
62 S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE,
63
64 /**
65 * Internal use only through macro. Do not use directly.
66 *
67 * @internal
68 * Add option entries into an Option_set::Opt_table that shall be output to an `ostream` to print a `Value_set`'s
69 * payload, using declare_option_for_output().
70 */
71 S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE,
72
73 /**
74 * Internal use only through macro. Do not use directly.
75 *
76 * @internal
77 * Compare recently parsed values in Option_set::m_iterable_values_candidate to the canonical
78 * Option_set::m_values values, using Option_set::scan_parsed_option().
79 */
80 S_COMPARE_PARSED_VALS,
81
82 /**
83 * Internal use only through macro. Do not use directly.
84 *
85 * @internal
86 * Load values in Option_set::m_values_candidate and Option_set::m_iterable_values_candidate from a
87 * ready-made `Value_set` -- as if the latter was parsed from some config source --
88 * using load_option_value_as_if_parsed().
89 */
90 S_LOAD_VALS_AS_IF_PARSED,
91
92 /**
93 * Internal use only through macro. Do not use directly.
94 *
95 * @internal
96 * Validate values stored -- perhaps manually in code, not through parsing -- in a `Value_set` structure
97 * using the same per-individual-option validator checks as configured into Option_set::m_opts_for_parsing
98 * via Declare_options_func_call_type::S_FILL_PARSING_ROLE_OPT_TABLE, using validate_parsed_option().
99 */
100 S_VALIDATE_STORED_VALS
101 }; // enum class Declare_options_func_call_type
102
103 // Methods.
104
105 /**
106 * Internal-through-macro helper function; the user shall not call this directly but only through
107 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
108 *
109 * @internal
110 * Loads an entry into `target_opts` which will enable the proper text to appear for that option
111 * in Option_set::values_to_ostream() or Option_set::log_values(): including its name, default value, current values,
112 * and description.
113 * @endinternal
114 *
115 * @tparam Value
116 * Type of the value inside a `Value_set` object. It must be reasonably copyable; and it must be supported by
117 * some version (including specialization(s) and overload(s)) of value_to_ostream().
118 * @param name
119 * See Option_set::declare_option_for_parsing().
120 * @param target_opts
121 * The `Opt_table` that shall be filled out for output to `ostream`.
122 * @param value_default
123 * See Option_set::declare_option_for_help().
124 * @param current_value
125 * Current value to show to the user.
126 * @param description
127 * See Option_set::declare_option_for_help().
128 */
129 template<typename Value>
130 static void declare_option_for_output(util::String_view name, opts::options_description* target_opts,
131 const Value& value_default, const Value& current_value,
132 util::String_view description);
133
134 /**
135 * Internal-through-macro helper function; the user shall not call this directly but only through
136 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
137 *
138 * @internal
139 * Runs the validator function, with the same semantics as in declare_option_for_parsing(), for the given
140 * option. If the validation passes, no-op. If the validation fails throws exception containing (in its message)
141 * a user-friendly description of what failed.
142 * @endinternal
143 *
144 * @tparam Value
145 * Type of the value inside a `Value_set` object. It must be reasonably copyable.
146 * @param name
147 * See Option_set::declare_option_for_parsing().
148 * @param value
149 * Value to validate.
150 * @param validator_func_moved
151 * See Option_set::declare_option_for_parsing().
152 * @param validator_cond_str
153 * See Option_set::declare_option_for_parsing().
154 */
155 template<typename Value>
156 static void validate_parsed_option(util::String_view name, const Value& value,
157 Function<bool (const Value& val)>&& validator_func_moved,
158 util::String_view validator_cond_str);
159
160protected:
161 // Types.
162
163 /**
164 * Short-hand for boost.program_options config options description, each of which is used for parsing and/or
165 * describing (to humans) one or more config option/its value. boost.program_options allows for a wide variety
166 * of features and capacities for this object type, but our goal is to use it for our specific and more
167 * constrained config context, and hence we use this type in more specific/constrained ways which we describe
168 * here.
169 *
170 * We use it in our context in two primary roles; and since storage space and processor cycle are not
171 * practically affected, we tend to store separate copies of an `Opt_table` in *each* role (possibly
172 * even more than 1, as there can be sub-roles of each).
173 * The roles are:
174 * - Parsing: Used during a parsing pass from a given config source (like a config file) solely to parse
175 * values and store the resulting bits into a target `Values` and a mirroring
176 * `Iterable_values`. `opts::{parse_*|store}()` and other such functions take
177 * this `Opt_table` and a config source as input. Such an `Opt_table`
178 * stores a table of options; for each option storing:
179 * - Name: A string to be found in a config source like a config file.
180 * - C++ type: Essentially a size in bytes plus a `>>` stream input operator for reading values
181 * into the target location from the config source.
182 * - Target location: Pointer to the actual value, within a `Values`, which to load with the parsed bits
183 * obtained from the config source.
184 * - NOTE: There is *no* default value stores in this *parsing-role* `Opt_table`.
185 * We expect each target value to already contain the default before parsing begins; if the option
186 * is not present in the particular config source, then that value remains untouched. Moreover note that
187 * the pre-parse value is not necessarily the user-specified default per se; it might be, for example,
188 * the value set by the previous config source's parsing phase; or even (in the case of dynamic option
189 * sets) in a much earlier config application.
190 * - Note: There's no reason to store a textual description for this role, as the computer doesn't care
191 * about human explanations when parsing.
192 * - Output: Used in various settings to print out -- to humans -- both the semantic info and actual values of
193 * the options maintained by an Option_set. To use this capability of boost.program_options -- which
194 * it intends as help text, but we can leverage it in slightly different roles also -- simply
195 * output this *output-role* `Opt_table` via a stream `<<`. For each option:
196 * - Name: Same as for the *parsing-role*.
197 * - Description: Textual description of the meaning/semantics of the option.
198 * - If it's not output of help text/similar, then description might be omitted, depending on usefulness.
199 * - C++ type: Essentially a size in bytes plus a `<<` stream output operator for writing values
200 * via standard stream output.
201 * - Value: A pointer to a value to print out along with the previous name and description.
202 * boost.program_options supports printing in one 2 ways:
203 * - Via `<<` stream output operator. In this case that operator needs to be (conceptually) stored too.
204 * - As an explicit string. Sometimes, for human readability, we might want to provide custom output
205 * for certain types beyond what `<<` would do; for example if we store a `Fine_duration` (typically
206 * outputting which would be in nanoseconds, because it usually stores nanoseconds), it's often more
207 * convenient to round down (if precision would not be lost) and output using coarser units like sec or
208 * even minutes, etc. In this case we would prepare this value as an `std::string` and store that;
209 * instead of storing a value in the original type as in *parsing-role* `Opt_table`.
210 * At least these reasons exist to print this value:
211 * - As the *default* in a help message. The user would glean that not specifying a value in any config
212 * source will lead to this value being taken.
213 * - As the *current* stored value after the last round of parsing. If it is also desired to output
214 * a default at the same time, it can be added into the description.
215 */
216 using Opt_table = opts::options_description;
217
218 // Methods.
219
220 /**
221 * Returns a function that wraps a `Value`->Boolean validator function, as passed to
222 * declare_option_for_parsing() and others, in code that will throw an exception with a human-useful message
223 * if that validator function indicates the `Value` passed to it is invalid; else will no-op.
224 *
225 * @tparam Value
226 * See Option_set::declare_option_for_parsing().
227 * @param name
228 * See Option_set::declare_option_for_parsing(). For nice human-readable message formation.
229 * @param validator_func_moved
230 * See Option_set::declare_option_for_parsing().
231 * @param validator_cond_str
232 * See Option_set::declare_option_for_parsing(). For nice human-readable message formation.
233 * @return See above.
234 */
235 template<typename Value>
236 static Function<void (const Value& val)> throw_on_invalid_func
237 (util::String_view name,
238 Function<bool (const Value& val)>&& validator_func_moved,
239 util::String_view validator_cond_str);
240 /* @return Function that acts as described above.
241 * @todo @return just above breaks Doxygen for some reason (bug). Work around it or something. */
242}; // class Option_set_base
243
244/**
245 * The core config-parsing facility, which builds parsing/comparison/output capabilities on top of a given
246 * simple config-holding object, of the type `Value_set`, a template argument to this class template.
247 *
248 * ### General use pattern ###
249 * - First, create your `Value_set` (named however you want, of course) `struct`. The values stored therein must
250 * be reasonably deep-copyable; must have standard-stream `<<` and `>>` operators; and must reasonably implement
251 * `==` comparison. `Value_set` itself must be copy-constructible and copy-assignable in a reasonable way.
252 * Lastly, and very importantly, the no-arg ctor `Value_set()` must initialize all configured
253 * members to reasonable defaults: it is not possible to declare options as "required."
254 * - If you use the optional features mutable_values_copy(), #Mutable_values_ptr, and/or #Values_ptr, then, also,
255 * `Value_set` shall derive from util::Shared_ptr_alias_holder. (Don't worry: it's easy.)
256 * - Next, write a function fitting Option_set::Declare_options_func, which shall be used (when passed into
257 * Option_set ctor) by Option_set to enable parsing (and human-readable help/other output to streams) of various
258 * members of `Value_set`, which officially turns them into *options*.
259 * - This is documented more fully in the `Option_set()` ctor doc header; but essentially this function
260 * shall use FLOW_CFG_OPTION_SET_DECLARE_OPTION() macro to enumerate every parseable member, turning it into
261 * an option *managed by* the containing Option_set.
262 * - (The data members that thus become options need not be direct members of `Value_set`. Composition via directly
263 * nested, and nested via pointer, `struct`s is supported. See FLOW_CFG_OPTION_SET_DECLARE_OPTION() doc header for
264 * details.)
265 * - Finally, create an instance of `Option_set<Value_set>`, and use parse_config_file() (or any other `parse_*()`
266 * methods that might exist) to parse things at will.
267 * - "At rest," Option_set is in CANONICAL state. values_candidate() returns null, and values() is the *canonical*
268 * (official, current) set of parsed config; originally it equals `Value_set()`.
269 * It returns a reference to immutable internally stored `Value_set`.
270 * - Invoking parse_config_file() (or any other `parse_*()`) either enters or continues PARSING state.
271 * In this state `*values_candidate()` starts equal to values(); and is then potentially modified by each
272 * `parse_*()` operation.
273 * - Call `canonicalize_candidate()` to set values() to `*values_candidate()`, thus canonicalizing the parsed
274 * values, and return to CANONICAL state.
275 * - Alternatively, if a `parse_*()` or `validate_values*()` fails, or any manual check of values_candidate() fails,
276 * one might want to instead call reject_candidate() to discard any parse attempts and return to CANONICAL state.
277 * - See notes in Validation below.
278 * - If one desires to model multiple dynamic updates, meaning calling canonicalize_candidate() multiple times --
279 * once for each update -- it may be desirable to use parse_direct_values() to load a baseline state at entry
280 * into PARSING state, and only then `parse_*()` from actual config source(s).
281 * (It would be trivial to save a previous state using values() to be that baseline state.)
282 *
283 * A typical expected scenario -- although more complex ones can be devised -- and one assumed by
284 * Config_manager -- is to have 2 `struct`s of config; one for static and one for dynamic config, the latter being
285 * something that can be parsed-into repeatedly over time, as new config is delivered to the process.
286 * In that case one would use one `Option_set<Static_value_set>` and one `Option_set<Dynamic_value_set>` and invoke
287 * their parsing at appropriate times (1+ times for the latter, once for the former). It is also possible to
288 * allow, on startup, to read both sets from the same config source(s) (e.g., a static config file); then read
289 * the dynamic `Option_set<>` from the dynamic config source(s) once (for any initial dynamic values overriding
290 * the initial baseline static ones); and after that accept any further updates of the latter config source(s), as
291 * they come in (e.g., if the dynamic config file is modified externally). The `allow_unregistered` argument
292 * to `parse_*()` allows 2+ `Option_set<>`s to share one config file.
293 *
294 * ### Validation ###
295 * Option validation *mandatorily* occurs in the following places.
296 * - When `parse_*()` is invoked, the config source (e.g., config file in case of parse_config_file()) may have
297 * plainly illegal contents, such as an option expecting a number but receiving alphanumerics, or a syntax
298 * error. This will cause `parse_*()` to indicate error.
299 * - If that passes, the individual-option-validator checks are performed *on values in fact read+parsed*.
300 * For example, a duration option may need to be non-negative, or a
301 * value in bytes must be a multiple of 4KiB. To provide any such additional
302 * *per-option*, *independent-of-other-options* conditions, use the `ARG_bool_validate_expr` argument to
303 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() when declaring the options (see above). This is somewhat reminiscent
304 * of writing `assert()` conditions. If a validator fails, `parse*()` will transparently fail, not dissimilarly
305 * to what happens if some line is straight-up illegal (previous bullet point).
306 *
307 * However this is likely insufficient validation in at least some use cases. You should also concern yourself with
308 * the following.
309 * - Note that parse_config_file() (and any similar `parse_*()` that scans+parses strings) will *only* validate
310 * (via individual-option-validator checks) values actually present in the config source.
311 * Defaults (from `Value_set()`) or baseline values (from parse_direct_values()) are not *mandatorily* checked.
312 * If one performs no additional validation calls, it will not be possible to know of bad defaults or baseline
313 * values, and one can canonicalize_candidate() invalid values. This is allowed, for flexibility, but in most cases
314 * one will want to reject_candidate() instead. The following abilities are provided to resolve this.
315 * For flexibility we do not here enforce any particular approach. It is your reponsibility to trace the
316 * possibilities. (For example Config_manager chooses certain conventions as it marshals various `Option_set`s.)
317 * - To validate defaults (default-constructed `Value_set`) themselves, call `validate_values(bool*)` overload --
318 * just after construction.
319 * (Informally: This is a stringent convention. It is possible to instead establish the convention wherein
320 * intentionally invalid defaults are allowed, to force that a config source must specify such values explicitly.
321 * In that case: one would not call validate_values() this way but rather... see next bullet point.)
322 * - To validate any defaults and/or baseline values that "got through" in PARSING mode, by virtue of not being
323 * present in a parsed config source (e.g., file) so far, call validate_values_candidate().
324 * (Informally: Typically you will not want to canonicalize_candidate() when the values_candidate() might have
325 * invalid values. So, just ahead of the decision to canonicalize_candidate()
326 * or reject_candidate(), call validate_values_candidate(). If it indicates failure, probably you'll want
327 * to reject_candidate().)
328 * - Lastly: parse_direct_values() intentionally does not validate the supplied `Value_set`.
329 * You are free to call validate_values_candidate() right after it to detect problems, if it suits your needs.
330 * (Informally: Whether this is necessary really depends on your setup, including the source of the values
331 * applied -- which may have already been validated -- and whether you're going to be calling
332 * validate_values_candidate() before canonicalizing anyway.)
333 * - You may also need a validation for inner consistency; e.g., if one setting must not exceed another setting.
334 * Do this yourself: Before calling canonicalize_candidate() check whatever is necessary in `*values_candidate()`.
335 * If you find problems call reject_candidate() instead.
336 * - Informal recommendation: It is better to not leave individual-option checking to this step; make use
337 * of FLOW_CFG_OPTION_SET_DECLARE_OPTION() to its fullest. This will result in more maintainable, reusable code
338 * and likely improve the experience (e.g., the individual validator machinery will always print the value of
339 * the offending option and, `assert()`-style, a string representation of the condition that failed).
340 *
341 * Lastly, and optionally, you may validate a given `Value_set` object "offline," meaning outside of any
342 * `Option_set<Value_set>` -- which need not even exist or ever parse anything; only the function fitting
343 * Option_set::Declare_options_func must exist, as-if for `Option_set` ctor. Simply call a validate_values()
344 * API on your object; it will yield failure given at least 1 invalid value. This can be useful, at least, when
345 * setting values manually through assignment (perhaps in a simpler program not requiring external config; or when
346 * unit-testing); just because you aren't parsing it through Option_set does not mean you don't want to sanity-check it
347 * for validity.
348 *
349 * ### Change detection ###
350 * For dynamic options, it may be necessary to detect whether the set of option values has changed or even individual
351 * changes (and then, in many cases, invoke config-changed hooks). Option_set is designed with this in mind, as it
352 * is able to provide such checks *without* forcing the user to write an `operator==(Value_set, Value_set)` (a laborious
353 * and error-prone procedure).
354 *
355 * Change detection occurs at the canonicalize_candidate() stage. That is, once you've parsed all the different config
356 * sources in a parse pass, canonicalize_candidate() will scan for changes. As of this writing that method will
357 * optionally return a Boolean indicating whether at least one value changed vs. the canonical values(). It is possible
358 * to add an API for detecting individual option changes. As I write this, it doesn't exist, but it may be added after
359 * I write this. Option_set is eminently capable of it; and in fact it at least logs an INFO message for each
360 * option that has changed, at canonicalize_candidate() time.
361 *
362 * ### Input/output ###
363 * Input of configurable values in `Value_set` is required to be able to parse the corresponding options (parse_*()).
364 * Output thereof is required in:
365 * - help_to_ostream(), log_help(): A usage/help message describing all options, and their default values,
366 * in human-friendly form.
367 * - values_to_ostream(), log_values(): A message describing all options, their default values, and their *current*
368 * (presumably parsed at some point) values, is printed (in human-friendly form). You can use this to output
369 * any `Value_set`, but if none is specified, it will log the canonical one (values()).
370 *
371 * For input (parsing), every configurable value in `Value_set` must have an `istream>>` operator.
372 *
373 * For output (in help and current-values output), every such value must have an `ostream<<` operator.
374 * In addition, it is sometimes desirable to further massage output in config help/current-values output but not
375 * in a general `ostream<<` operator for that type. (E.g., you would not want to, or be able to usually, override
376 * stream output for `std::string` or `chrono::duration`.) To do this, provide a specialization or overload of
377 * the free function `cfg::value_to_ostream<Value_set>()`. Its generic implementation simply forwards to
378 * `ostream<<`, but you can do something different. As of this writing we already provide an overload for
379 * `cfg::value_to_ostream<chrono::duration<...>>`, which will automatically print the duration (which is typically
380 * stored in nanoseconds) in the coarsest possible units that would lose no precision (e.g., 60billion nanoseconds =>
381 * "1 minute").
382 *
383 * ### Recommended conventions ###
384 * Firstly, it is recommended to store all durations in your `Value_set` as util::Fine_duration instead of using
385 * coarser units like `chrono::seconds()` or even `chrono::milliseconds()`. This tends to lead to more consistent
386 * and maintainable code, in the author's (ygoldfel) opinion, as util::Fine_duration *can* store durations expressed
387 * in essentially any units, without losing precision; *does* use the same underlying storage type -- `int64_t` --
388 * and hence presents no performance or overflow difficulties (usually); and changing the desired units of a duration
389 * config value is fairly common. Simply put `Fine_duration` supports 99.99999999% of units and use cases without
390 * perf overhead. *In addition*, `value_to_ostream<>` is overloaded in such a way as to output `Fine_duration`
391 * members of `Value_set` in the most convenient possible units -- automagically. So you're in no way forcing humans
392 * to work with nanoseconds: you can use any units in input and in code.
393 *
394 * @note For all duration values, config sources can specify *any* units (convertible without loss of precision to
395 * the specific `chrono` type); and indeed *should* specify units. E.g., "5 ms" and "5 milliseconds" will both
396 * work. Since util::Fine_duration is in nanoseconds internally, almost any conceivable units (from "hours" to
397 * "nanoseconds") are accepted equally well as inputs in config sources.
398 *
399 * Secondly, when working with non-durations, specify units as a suffix in the member name.
400 * - Exception: if it's in bytes, `_bytes` or `_b` shall be omitted.
401 * - If it's in multiples of bytes, use `_kb`, `_mb`, etc., knowing that this stands for KiB (1024), MiB (1024^2),
402 * etc. In the rare case that actual KILObytes, etc., are needed, spell it out: `_kilob`, `_megab`, etc.
403 * - I (ygoldfel) am not recommending `_kib`, etc., only because the convention in the relevant organization(s) is
404 * quite strong to say KB for KiB, etc.
405 * - If, for some reason, you must use a duration with a max resolution lower than ns, then use these suffixes:
406 * `_hr`, `_min`, `_sec`, `_msec`, `_usec`, `_nsec`. Please still use `chrono::duration`, though, even if you
407 * chose something other than `Fine_duration`. (Do not use an integer type.)
408 *
409 * @todo Add individual-option-changed detection API(s) in addition to the existing Option_set overall-value-set-changed
410 * detection API. This is contingent on a use case needing it. The existing code already detects this internally
411 * and logs a message for each changed option (in canonicalize_candidate()).
412 *
413 * @tparam Value_set
414 * The value type stored inside `*this`, and returned by values(). Requirements on this type are at least
415 * informally explained above.
416 */
417template<typename Value_set>
419 public Option_set_base,
420 public log::Log_context,
421 private boost::noncopyable
422{
423public:
424 // Types.
425
426 /**
427 * Short-hand for the template parameter type `Value_set`. E.g.: `Cool_option_set::Values`, where one aliased
428 * `Cool_option_set` to `Option_set<Cool_value_set>`.
429 */
430 using Values = Value_set;
431
432 /**
433 * Short-hand for ref-counted pointer to an immutable `Value_set` (config payload storable in an Option_set).
434 *
435 * The name is not, say, `Const_values_ptr`, because we would expect such objects to be passed around in `const`
436 * form most of the time, including when made accessible from within a config-holding API.
437 * When a mutable #Values is desired, one would typically create it from an Option_set by using mutable_values_copy().
438 */
439 using Values_ptr = typename Values::Const_ptr;
440
441 /// Short-hand for ref-counted pointer to a mutable #Values (config payload storable in an Option_set).
442 using Mutable_values_ptr = typename Values::Ptr;
443
444 /**
445 * Internal-use structure to use with #Declare_options_func callback. The user of the class need not understand this
446 * nor use it directly.
447 *
448 * @internal
449 *
450 * This `union`-y `struct` is used to pass values into `declare_option_*()`, `scan_parsed_option()`, or
451 * `validate_parsed_option()`.
452 * The `enum` specifies which part of the `union` is in effect; then each `struct` inside the `union` corresponds
453 * to that #Declare_options_func call purpose.
454 */
456 {
457 // Types.
458
459 /// Short-hand for type of #m_call_type.
461
462 // Data.
463
464 /// Why #Declare_options_func is being called.
466
467 /**
468 * The args to pass in depending on #m_call_type.
469 * @internal
470 * @todo `union` used inside Option_set implementation should be replaced with the type-safe, equally-fast,
471 * more-expressive `std::variant`. The author was not aware of the latter's existence when originally writing
472 * this.
473 */
474 union
475 {
476 /// Corresponds to Call_type::S_FILL_PARSING_ROLE_OPT_TABLE.
477 struct
478 {
479 /// `m_option_set->m_opts_for_parsing` shall be filled out.
481 /**
482 * `m_option_set->m_values_candidate`, the parsing-in-progress `Value_set` filled out whenever parsing using
483 * the completed Option_set::m_opts_for_parsing.
484 */
486 /**
487 * `m_option_set->m_values_default`: the defaults loaded into #m_values_candidate at the top of each
488 * parse, for any option specified as non-accumulating. (Any members whose corresponding option is a regular
489 * accumulating one is, in this context, ignored; instead the preceding parse's final value is kept unchanged.)
490 */
493
494 /// Corresponds to Call_type::S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE.
495 struct
496 {
497 /// `m_option_set->m_opts_for_help` shall be filled out.
499 /// `m_option_set->m_values_default`: defaults loaded into `Option_set::m_opts_for_help` will originate here.
502
503 /// Corresponds to Call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE.
504 struct
505 {
506 /**
507 * #m_target_opts shall be filled out based on the defaults earlier saved into #m_values_default and
508 * current values in #m_values_current.
509 */
510 opts::options_description* m_target_opts;
511 /// `m_option_set->m_values_default`: the defaults loaded into `*m_target_opts` will originate here.
513 /// The current values loaded into `*m_target_opts` (e.g., via description text) will originate here.
516
517 /// Corresponds to Call_type::S_COMPARE_PARSED_VALS.
518 struct
519 {
520 /**
521 * Canonical values in `m_option_set->values()` shall be compared with parsed values in
522 * `m_option_set->m_iterable_values_candidate`.
523 */
526
527 /// Corresponds to Call_type::S_LOAD_VALS_AS_IF_PARSED.
528 struct
529 {
530 /**
531 * Values in `*(m_option_set->values_candidate())` (and related) shall be loaded from #m_values_to_load, as-if
532 * parsed from some config source.
533 */
535
536 /**
537 * `m_option_set->m_values_candidate`, the structure to load from #m_values_to_load.
538 */
540
541 /// Values shall be copied from this external `Value_set`.
544
545 /// Corresponds to Call_type::S_VALIDATE_STORED_VALS.
546 struct
547 {
548 /// Each validator-enabled (via a #Declare_options_func) member in this structure shall be validated.
552 }; // struct Declare_options_func_args
553
554 /**
555 * Short-hand for the ever-important callback passed to the main Option_set constructor. The user of the class
556 * need not understand the meanings of the args, because the FLOW_CFG_OPTION_SET_DECLARE_OPTION() macro will
557 * take care of using them properly.
558 *
559 * @internal
560 *
561 * The idea is that the body of a user-provided callback of this type will consist of invoking
562 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each parseable option, in some consistent order, and that macro (which
563 * we control) will invoke handling of that particular option -- whose stored type can be anything and different from
564 * the other options', meaning we cannot iterate through them at runtime (nor is it possible at compile time without
565 * some kind of insane meta-programming). So, depending on what arguments we pass to this callback, the macro will --
566 * upon performing an operation only a macro can do (namely `#ARG_whatever` and the like) -- forward those args
567 * to a function templated on the type of the option's stored value, namely one of `declare_option_*()`,
568 * scan_parsed_option(), load_option_value_as_if_parsed(), or validate_parsed_option(). That function will then
569 * perform the needed handling for that particular option.
570 *
571 * See Declare_options_func_args for the possible args' meanings.
572 */
574
575 // Constructors/destructor.
576
577 /**
578 * Constructs an option set in CANONICAL state with a default-valued values() payload and options declared
579 * by synchronously invoking the callback `declare_opts_func()`. See below for details on the latter.
580 *
581 * Post-condition: `values()` is equal to `Values()`; values_candidate() is null (so the state is initially
582 * CANONICAL). (Therefore `Value_set` no-args ctor must by definition be written so as to initialize all its
583 * relevant members to their defaults. Recall `Value_set` is a template parameter type with certain requirements.)
584 *
585 * Use `parse_*()` (once per config source) and `canonicalize_candidate()` (once) to move to PARSING state and back
586 * to CANONICAL state respectively.
587 *
588 * ### Declaring options via `declare_opts_func()` callback ###
589 * `declare_opts_func` above denotes an internally saved callback `move()`d from the corresponding arg
590 * `declare_opts_func_moved`. `declare_opts_func()` is also invoked synchronously
591 * from within various relevant output APIs; their doc headers
592 * mention it. (E.g., invoking stream output `os << *this` will call `declare_opts_func()`.)
593 *
594 * Informally, the callback must declare every parseable (and therefore stream-printable-semantic-description-having)
595 * option, linking it to some data member within a target `Values` whose address is passed to the callback as an
596 * arg; in a consistent order; and as many times through the lifetime of `*this` (including during this ctor) as
597 * `*this` deems necessary. Use FLOW_CFG_OPTION_SET_DECLARE_OPTION() to describe all the options.
598 * Formally, it must follow the following exact rules:
599 * - Its signature right above the `{ body }` must name its args *exactly* as shown in the value of the
600 * #Declare_options_func alias.
601 * - Its body must execute N>=1 invocations of the macro
602 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() -- see its doc header -- each corresponding to a distinct
603 * data member of `Values`, always in the same order and the same N. Clues as to how this works:
604 * - The naming of the args is so specific in order to make the macro minimally concise to use.
605 * - Macro machinery will auto-determine the main name of the option (as seen in config sources like files)
606 * based on the naming of the `m_` member.
607 * - The macro will call a public API that is not to be called directly, passing in various
608 * data just mentioned, plus the text description passed to it (if relevant) and the option name passed to it.
609 *
610 * @param logger_ptr
611 * Logger to use for subsequently logging.
612 * @param nickname
613 * Brief string used for logging subsequently.
614 * @param declare_opts_func_moved
615 * See above.
616 */
617 explicit Option_set(log::Logger* logger_ptr, util::String_view nickname,
618 Declare_options_func&& declare_opts_func_moved);
619
620 // Methods.
621
622 /**
623 * Externally immutable internally stored canonical (current) config values as last constructed or parsed,
624 * whichever happened more recently.
625 *
626 * @see mutable_values_copy() to get a copy.
627 *
628 * @return See above.
629 */
630 const Values& values() const;
631
632 /**
633 * Convenience method that heap-allocates a copy of the internally stored `values()` and wraps in a ref-counted
634 * handle suitable for speedy passing around the rest of the application.
635 *
636 * @return See above.
637 */
639
640 /**
641 * Returns null in CANONICAL state; or in PARSING state a pointer to the not-yet-canonical values after the last
642 * successful `parse_*()` API call.
643 *
644 * Rationale: It is supplied publicly in case the caller wans to log it or something; or perhaps to check
645 * for current state (CANONICAL if and only it returns null).
646 *
647 * @return See above.
648 */
649 const Values* values_candidate() const;
650
651 /**
652 * Validates the current contents of values() using the validators `*this` `Option_set<Value_set>` is configured
653 * to use via constructor. If at least one option
654 * is invalid according to a validator declared by a FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocation in
655 * `declare_opts_func()`, then this function shall indicate failure; else success.
656 *
657 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
658 * On success simply returns or sets `*success_or_null` to `true` respectively. In the exception case the message
659 * will indicate every reasonable detail about the option value that went wrong. This info is logged regardless.
660 *
661 * Informally, the use case driving the presence of this overload is discussed in the Validation section of our
662 * class doc header; see that. To restate: If you want to stringently ensure the defaults are themselves valid,
663 * simply invoke this right after construction, at which point values() are by definition at their defaults.
664 *
665 * @param success_or_null
666 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
667 */
668 void validate_values(bool* success_or_null = 0) const;
669
670 /**
671 * Validates an arbitrary `Value_set`, as parseable by *an* `Option_set<Value_set>`, according to the
672 * given option-registering function suitable for `Option_set<Value_set>` constructor. If at least one option
673 * is invalid according to a validator declared by a FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocation in
674 * `declare_opts_func()`, then this function shall indicate failure; else success.
675 *
676 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
677 * On success simply returns or sets `*success_or_null` to `true` respectively. In the exception case the message
678 * will indicate every reasonable detail about the option value that went wrong. This info is logged regardless.
679 *
680 * Use this, at least, if you've filled out a `Value_set` through manual assignment or some other source, rather
681 * than parsing through an Option_set -- and don't even *use* an Option_set, as you don't parse from config --
682 * but still want to check it for validity.
683 *
684 * You can also use the non-`static` overload to reuse the #Declare_options_func passed to an existing
685 * parsing-capable `Option_set<Value_set>`.
686 *
687 * @param logger_ptr
688 * Logger to use for subsequently logging.
689 * @param values_to_validate
690 * The values in this structure shall be validated in the same way as they are upon parsing
691 * by a hypothetical `Option_set<Value_set>` into whose ctor the same `declare_opts_func` were passed.
692 * @param declare_opts_func
693 * See Option_set constructor.
694 * @param success_or_null
695 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
696 */
697 static void validate_values(log::Logger* logger_ptr,
698 const Values& values_to_validate, const Declare_options_func& declare_opts_func,
699 bool* success_or_null = 0);
700
701 /**
702 * Validates an arbitrary `Value_set`, using the same validators `*this` `Option_set<Value_set>` is configured to use
703 * when parsing config sources. This essentially means using the `static` overload and passing to it
704 * `declare_opts_func` equal to the one earlier passed by the user to `*this` constructor. The success/failure
705 * semantics are identical to the other overload's (see that doc header).
706 *
707 * You can also use the `static` overload, if you aren't at all parsing from config sources but still want to
708 * validate.
709 *
710 * @param values_to_validate
711 * The values in this structure shall be validated in the same way as they are upon `this->parse_*()`ing from
712 * a config source.
713 * @param success_or_null
714 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
715 */
716 void validate_values(const Values& values_to_validate, bool* success_or_null = 0) const;
717
718 /**
719 * Equivalent to `validate_values(success_or_null)` but validates `*values_candidate()` instead of values().
720 * Behavior is undefined (assertion may trip) if not in PARSING mode currently (i.e., values_candidate() is null).
721 *
722 * Informally, the use case driving the presence of this overload is discussed in the Validation section of our
723 * class doc header; see that. To restate: If you want to ensure no invalid defaults or baseline values have
724 * "gotten through" in the current PARSING state, then invoke this -- particularly just ahead of the decision to
725 * either canonicalize_candidate() or reject_candidate().
726 *
727 * @param success_or_null
728 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
729 */
730 void validate_values_candidate(bool* success_or_null = 0) const;
731
732 /**
733 * Writes a multi-line user-suitable representation of the current values in a #Values object, at some point
734 * perhaps initialized or parsed by `*this`, to the given stream. This should typically be preceded by
735 * a newline but not followed by one, unless one desires a blank line there.
736 *
737 * @param os
738 * Stream to which to serialize.
739 * @param values_or_null
740 * Values to serialize; if null then we act as-if it's `&(values())`.
741 */
742 void values_to_ostream(std::ostream& os, const Values* values_or_null = 0) const;
743
744 /**
745 * Logs the given values payload using values_to_ostream().
746 *
747 * @param values_or_null
748 * See values_to_ostream().
749 * @param summary
750 * Brief summary of what this payload represents.
751 * @param sev
752 * Severity to use for the log message.
753 */
754 void log_values(util::String_view summary, const Values* values_or_null = 0, log::Sev sev = log::Sev::S_INFO) const;
755
756 /**
757 * Prints a multi-line help message about the set of options that `*this` can parse. This should typically
758 * be preceded by a newline but not followed by one, unless one desires a blank line there.
759 *
760 * @param os
761 * Stream to which to serialize.
762 */
763 void help_to_ostream(std::ostream& os) const;
764
765 /**
766 * Logs a multi-line help message using help_to_ostream().
767 *
768 * @param summary
769 * Brief summary of the help message.
770 * @param sev
771 * Severity to use for the log message.
772 */
773 void log_help(util::String_view summary, log::Sev sev = log::Sev::S_INFO) const;
774
775 /**
776 * Enters into (from CANONICAL state) or continues in PARSING state by parsing the config source in the form of
777 * the given file in the file-system. On success values_candidate() is updated; on failure it is untouched.
778 *
779 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
780 * On success simply returns or sets `*success_or_null` to `true` respectively. Information is logged regardless.
781 *
782 * A failure may occur due to invalid contents in the config source. A failure may occur due to a validator
783 * condition failed (see FLOW_CFG_OPTION_SET_DECLARE_OPTION()).
784 *
785 * However: the latter applies only to settings actually specified in `cfg_path` file. Any default or baseline or
786 * otherwise-previously-set, but not overridden in `cfg_path`, values are not validated -- neither before nor after
787 * scanning `cfg_path`. You are, however, free to do so by next (or previously, or both) invoking
788 * validate_values_candidate(). (If "previously," and not yet in PARSING state, then use validate_values().)
789 * See Validation in class doc header for discussion.
790 *
791 * @see canonicalize_candidate() which will finalize the values_candidate() constructed so far.
792 * @see reject_candidate() which will snap back to CANONICAL state rejecting any successful parsing done.
793 * In particular it would make sense in many cases to do this if parse_config_file() indicates failure.
794 * @param cfg_path
795 * Path to parse.
796 * @param allow_unregistered
797 * If `true`, if an unknown option is encountered it may be allowed (not considered a failure),
798 * subject to `allowed_unregistered_opts_or_empty`, though an INFO
799 * message is still logged for each; if `false` it is an error like any other illegal config setting.
800 * One reason for `true` is if another `Option_set<>` will be parsed from the same config source.
801 * Another is if there could be forward- or backward-compatibility concerns.
802 * @param allowed_unregistered_opts_or_empty
803 * Meaningful only if `allow_unregistered == true`, this is the list of all option names (compared
804 * case-sensitively) that will not cause a validation error; or empty to allow *all* unknown options.
805 * @param success_or_null
806 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
807 */
808 void parse_config_file(const fs::path& cfg_path, bool allow_unregistered,
809 bool* success_or_null = 0,
810 const boost::unordered_set<std::string>& allowed_unregistered_opts_or_empty = {});
811
812 /**
813 * Enters into (from CANONICAL state) or continues in PARSING state by simply setting `*values_candidate()`
814 * to equal the #Values payload given as an argument. Typically precedes other `parse_*()` calls such as
815 * parse_config_file(). If already in PARSING state, note that any changes accumulated in `*values_candidate()`
816 * so far will be overwritten entirely.
817 *
818 * The values in `src_values` are not checked for validity according to the validators configured
819 * in the FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocations in `declare_opts_func()` passed to ctor.
820 * You are, however, free to do so by next invoking validate_values_candidate().
821 * See Validation in class doc header for discussion.
822 *
823 * ### Rationale ###
824 * This is useful, particularly, when one plans to repeatedly apply updates to one `*this`, but a certain *baseline*
825 * state is desired before each update. Consider the example of an Option_set that stores dynamically changeable
826 * values. Suppose each update consists only of a single `parse_config_file(F)` call, where `F` is some file that
827 * might get changed at various times to deliver dynamic updates. Then consider this series:
828 * -# Initial Option_set construction. End state: `values() == Values()` (default).
829 * -# First update occurs: `parse_config_file(F)`, followed by canonicalize_candidate().
830 * End state: `values()` == defaults + changes in file `F` at time 1.
831 * -# Second update occurs: `parse_config_file(F)`, followed by canonicalize_candidate().
832 * End state: `values()` == defaults + changes in file `F` at time 1 +
833 * changes in file `F` at time 2.
834 * -# (etc.)
835 *
836 * In this case values() is incrementally changed by each dynamic update, as the file `F` keeps changing.
837 * E.g., if at time 1 it contained only option A, and at time 2 it contained only option B, then values()
838 * would contain both time-1 option A value and time-2 option B value -- even though the file `F` at
839 * time 2 contains only the latter. This *might* be what you want, but since it's fully incremental, there are
840 * some usability landmines. Mainly: If one looks at `F` contents at any given time, they can't know what the
841 * resulting state would be; it depends on what updates preceded it.
842 *
843 * To resolve this, one can save a *baseline* state of values() by copy; and then apply it via this
844 * parse_direct_values() call before parsing the file in each dynamic update. The baseline state could just be
845 * defaults (`Values()`), or it could come from some special "baseline" config file that is not `F` which one knows
846 * to never change. (Such a file could also typically store static config managed by a separate Option_set.)
847 *
848 * So then the sequence might become not parse_config_file(), canonicalize_candidate(), parse_config_file(),
849 * canonicalize_candidate(), ...; but rather:
850 * -# Baseline parse: `parse_config_file(B)`, canonicalize_candidate().
851 * -# Save values() copy into `Values baseline`.
852 * -# Update 0: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
853 * -# Update 1: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
854 * -# Update 2: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
855 * -# ...
856 * (The first `parse_direct_values(baseline)` here is a no-op but included for clarity/symmetry, as usually one
857 * would just do the same thing for each update.)
858 *
859 * It is also sometimes desirable to "rewind" the state of values_candidate() (by first memorizing it, then
860 * parse_config_file() or similar, then if some value in the resulting value set indicates the file should not
861 * apply after all, parse_direct_values() to "undo." Config_manager uses it when its multi-source feature is
862 * engaged -- `commit = false`.)
863 *
864 * @param src_values
865 * The values set loaded into `*values_candidate()`, as-if parsed from some config source.
866 */
867 void parse_direct_values(const Values& src_values);
868
869 /**
870 * In PARSING state enters CANONICAL state, finalizing values() from values_candidate().
871 * Any cumulative changes are INFO-logged on a per-changed-option basis; and if at least one option's value indeed
872 * changed then `*change_detected` is set to `true` (else `false`). (Leave the arg null, if you do not care.)
873 *
874 * @note Individual option validation should be done via the validation condition argument to
875 * FLOW_CFG_OPTION_SET_DECLARE_OPTION(). However, it may be necessary to perform a check for internal
876 * consistency among the final values. The proper time to do this is just before calling
877 * canonicalize_candidate(). If the final check fails, typically one would instead call
878 * reject_candidate().
879 *
880 * @param change_detected
881 * If null, ignored; otherwise `*change_detected` is set to `true` if a setting changed; else `false`.
882 */
883 void canonicalize_candidate(bool* change_detected = 0);
884
885 /**
886 * In PARSING state, returns to CANONICAL state, as if no parse attempts have occurred. In CANONICAL state, a no-op.
887 * Calling this is typically a good idea when a `parse_*()` attempt indicates failure.
888 */
889 void reject_candidate();
890
891 /**
892 * Internal-through-macro helper function; the user shall not call this directly but only through
893 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
894 *
895 * @internal
896 * Loads an entry into #m_opts_for_parsing which will enable the parsing into `*target_value` from config sources
897 * such as config files.
898 * @endinternal
899 *
900 * @tparam Value
901 * Type of the value inside a #Values object. It must be reasonably copyable.
902 * @param name
903 * Main option name: as specified by the user in a config source.
904 * As of this writing FLOW_CFG_OPTION_SET_DECLARE_OPTION() uses macro magic to automatically form this
905 * from the identifier name within a #Values object (e.g., `Value_set::m_cool_option` => "cool-option").
906 * @param target_value
907 * When deserializing a value from a config source, the bits shall be written there.
908 * This must point inside `m_values_candidate`.
909 * @param value_default_if_no_acc
910 * Usually -- with regular (accumulating) options -- null; otherwise pointer to the default value for
911 * `*target_value` (as from `Values()`), inside `m_values_default`. In the latter case (non-null) this
912 * indicates this is an option marked by the user as non-accumulating
913 * (see FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() and similar), meaning each time a config source
914 * (e.g., a file) is parsed `*target_value` is first reset to this default; then overwritten with the value in
915 * the config source if, else left at the default. An accumulating option in the latter case would instead
916 * keep its existing value already in `*target_value`.
917 * @param validator_func_moved
918 * Function F, such that F(V) shall be called from `parse_*()` when this option's successfully parsed value V
919 * is being validated before final storage in `m_values_candidate`. Return `false` if the validation
920 * shall fail; otherwise return `true`.
921 * @param validator_cond_str
922 * String containing the Boolean code that `validator_func_moved()` would need to evaluate to `true` to pass
923 * validation. As of this writing FLOW_CFG_OPTION_SET_DECLARE_OPTION() uses macro magic to automatically form
924 * this from a Boolean expression fragment.
925 */
926 template<typename Value>
927 void declare_option_for_parsing(util::String_view name, Value* target_value, const Value* value_default_if_no_acc,
928 Function<bool (const Value& val)>&& validator_func_moved,
929 util::String_view validator_cond_str);
930
931 /**
932 * Internal-through-macro helper function; the user shall not call this directly but only through
933 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
934 *
935 * @internal
936 * Loads an entry into #m_opts_for_help which will enable the proper help text to appear for that option
937 * in help_to_ostream() or log_help().
938 * @endinternal
939 *
940 * @tparam Value
941 * Type of the value inside a #Values object. It must be reasonably copyable; and it must be supported by
942 * some version (including specialization(s) and overload(s)) of value_to_ostream().
943 * @param name
944 * See declare_option_for_parsing().
945 * @param value_default
946 * Default value to show to the user.
947 * @param description
948 * The description text to show to the user.
949 */
950 template<typename Value>
951 void declare_option_for_help(util::String_view name, const Value& value_default, util::String_view description);
952
953 /**
954 * Internal-through-macro helper function; the user shall not call this directly but only through
955 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
956 *
957 * @internal
958 * Scans a canonical (current) value in #m_values, comparing it to the (possibly) parsed one in
959 * #m_iterable_values_candidate, erasing it from the latter if and only if it is unchanged.
960 * @endinternal
961 *
962 * @tparam Value
963 * Type of the value inside a #Values object. It must have a meaningful `==` operation.
964 * @param name
965 * See declare_option_for_parsing().
966 * @param canonical_value
967 * Current value in `m_values`.
968 */
969 template<typename Value>
970 void scan_parsed_option(util::String_view name, const Value& canonical_value);
971
972 /**
973 * Internal-through-macro helper function; the user shall not call this directly but only through
974 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
975 *
976 * @internal
977 * To be called only when #m_parsing is `true`, sets a value in #m_values_candidate to equal one
978 * in an external #Values object; and mirrors this in #m_iterable_values_candidate to maintain its invariant.
979 * As a result, it is as-if that value was parsed from some config source such as a file.
980 * Note that, as with parsing from actual config sources like files, one must still scan_parsed_option()
981 * at some point to slim down #m_iterable_values_candidate to store merely the changed options.
982 * @endinternal
983 *
984 * @tparam Value
985 * Type of the value inside a #Values object. It must have a meaningful `=` operation.
986 * @param name
987 * See declare_option_for_parsing().
988 * @param target_value
989 * Target value inside `m_values_candidate`.
990 * @param source_value
991 * Value to load into `*target_value` (as-if it was parsed from a config source).
992 */
993 template<typename Value>
994 void load_option_value_as_if_parsed(util::String_view name, Value* target_value, const Value& source_value);
995
996 /**
997 * Return `true` if and only if the option-declaring function passed to the constructor declared no options.
998 * This value is always the same for a given `*this`.
999 *
1000 * ### Rationale ###
1001 * While likely of little value when the user instantiates an Option_set directly, this can be useful in
1002 * generic meta-programming, wherein multiple `Option_set`s might be instantiated at compile time, but the
1003 * coder doesn't know how many while coding. For example Config_manager uses null() to bypass confusingly-logging
1004 * parse_config_file() calls on empty Option_set objects, such as if a static config set has no dynamic config
1005 * counterpart.
1006 *
1007 * @return See above.
1008 */
1009 bool null() const;
1010
1011 /**
1012 * Returns set of all option names declared by the option-declaring function passed to the constructor.
1013 * This can be useful to supply to parse_config_file(), for example, when parsing another Option_set from the
1014 * same file: then other options can be passed to that function as not causing a parse error if encountered;
1015 * hence 2 or more `Option_set`s can parse_config_file() the same file despite having disjoint option name sets --
1016 * yet totally extraneous options in none of the `Option_set`s would cause an error as desired.
1017 *
1018 * This returns the same value (and reference) each time it is called.
1019 *
1020 * @return See above.
1021 */
1022 const boost::unordered_set<std::string>& option_names() const;
1023
1024 // Data.
1025
1026 /// See `nickname` ctor arg.
1027 const std::string m_nickname;
1028
1029private:
1030 // Types.
1031
1032 /**
1033 * Similar to a boost.program_options parsing results `variables_map`, which conceptually mirrors the results of a
1034 * parsing operation in the target `Values` structure,
1035 * mapping `string` to variable-type values. As explained in the doc header for
1036 * Option_set::Opt_table, a parsing operation takes in a config source (config file, etc.)
1037 * and a *parsing-role* `Opt_table`; and outputs a `Values` and an object of the present type.
1038 * `Values` is the main point Option_set exists; it's what the application accesses, via simple
1039 * `->m_*` access of various values, to observe the parsed config -- so why do we need `Iterable_values`?
1040 *
1041 * The reason we use this is: we need to be able to iterate through all the options that were found in a
1042 * parsing pass, at least to detect changes as needed at least for dynamic option features (triggering
1043 * on-changed hooks, etc.). (Requiring a manual `operator==(Value_set, Value_set)` or similar is too onerous
1044 * and error-prone for the implementor of the `Value_set`, as every `->m_` would require manual mention.)
1045 *
1046 * A `variables_map` is classically filled via the aforementioned parsing operation `opts::store()`;
1047 * and is itself an `std::map`. Hence one can copy all the pairs from that into an #Iterable_values; and
1048 * an #Iterable_values can be manually pre-filled with pre-parse values from `Values` for easy
1049 * comparison after the `store()` (and other such algorithms). Note, also, it is deep-copyable, as long as
1050 * all types stored within are deep-copyable.
1051 *
1052 * ### Rationale ###
1053 * - Why not simply use `opts::variables_map`? It is after all very close to what this is. Answer: I (ygoldfel)
1054 * started that way, but its API is a bit hairy, and it's scary to have to depend on how boost.program_options
1055 * will act when `variables_map` isn't empty before the parsing begins, for example (e.g., `store()` docs say
1056 * it will NOT overwrite any "non-defaulted" value -- having to rely on such nuances doesn't seem great).
1057 * It is safer, if slower (but we don't care), to treat a fresh `variables_map` that's `store()`d into as an
1058 * intermediary.
1059 * - Why not `unordered_map`? It's faster after all. Answer: An alphabetical iteration order seems nice when
1060 * (e.g.) iterating over the keys to show differences in values to humans.
1061 */
1062 using Iterable_values = std::map<std::string, boost::any>;
1063
1064 // Data.
1065
1066 /// See null().
1068
1069 /// See option_names().
1070 boost::unordered_set<std::string> m_opt_names;
1071
1072 /**
1073 * The #Opt_table-filling, or #m_iterable_values_candidate-scanning, callback passed to the constructor and invoked
1074 * with various input args (see #Declare_options_func doc header). Depending on those args it will either fill
1075 * `Opt_table` in various roles (see #Opt_table doc header), or it will scan the values just loaded into
1076 * #m_iterable_values_candidate (e.g., for equality to the values in canonical #m_values).
1077 */
1079
1080 /// See values().
1082
1083 /// Copy of values() when it is first constructed; i.e., the defaults.
1085
1086 /// See values_candidate() -- `true` if and only if that returns non-null (PARSING state; else CANONICAL state).
1088
1089 /// See values_candidate(). When that returns null (`m_parsing == false`) this value is meaningless.
1091
1092 /**
1093 * Structure mirroring #m_values_candidate, where the values are the parsing-enabled members of #m_values_candidate,
1094 * and each value's key is it main config option name; if an option has not changed between #m_values and
1095 * #m_values_candidate (according to its `==` operator), then that option's key is omitted. In particular
1096 * it is `.empty() == true` when in CANONICAL mode (`m_parsing == false`).
1097 */
1099
1100 /**
1101 * The *parsing-role* #Opt_table (see extensive explanation in #Opt_table doc header). Briefly: it's used
1102 * to actually load `*m_values_candidate` -- and ultimately #m_values -- with values parsed from config sources such
1103 * as files. This must not change after the first `parse_*()` API begins execution.
1104 */
1106
1107 /**
1108 * The *output-role* #Opt_table, help-text sub-role (see extensive explanation in #Opt_table doc header).
1109 * Briefly: it's used to output help text to output streams for user's convenience, in fact simply by piping it
1110 * to an `ostream` via `<<`. This should not change after the first `parse_*()` API begins execution (lest the
1111 * user become confused).
1112 */
1114}; // class Option_set
1115
1116// Free functions: in *_fwd.hpp.
1117
1118// Macros.
1119
1120/**
1121 * Macro the user must use, as prescribed in the flow::cfg::Option_set constructor doc header discussing
1122 * `declare_opts_func` in their flow::cfg::Option_set::Declare_options_func callback, when declaring each individual
1123 * config option as linked to a given data member of their config-bearing `struct` used as the `Value_set` in
1124 * `Option_set<Value_set>`.
1125 *
1126 * @note The resulting option name, as found in config sources such as config files, will be auto-computed from
1127 * the string `#ARG_m_value`. So if `ARG_m_value` is `m_cool_thing.m_cool_guy`, then the option name
1128 * might be computed to be, say, `"cool-thing.cool-duration"`, with a config file line:
1129 * `"cool-thing.cool-duration=2 milliseconds"`.
1130 * @note It may be desirable to *not* auto-name a given option; a known use case for this is legacy option names,
1131 * if renaming is difficult in the field. Use FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED() if indeed
1132 * you want to custom-name your option.
1133 *
1134 * @param ARG_m_value
1135 * Supposing `Value_set` is the template arg to `Option_set`, and `Value_set V` is being registered,
1136 * then `ARG_m_value` is such that `V.ARG_m_value` is the scalar into which the particular config option shall
1137 * be deserialized. It shall be a scalar data member name, optionally within a compound data member, which is
1138 * itself optionally within a compound, and so forth, within `Value_set V`. Each optional compounding may
1139 * be via simple object composition (`.`) or pointer dereference (`->`).
1140 * Examples: `m_direct_member`, `m_group_obj.m_indirect_member`,
1141 * `m_group.m_group1->m_pointed_group->m_cool_scalar`. Reminder: each of these must be accessible within
1142 * a `Value_set` via `.` compounding. E.g.: `V.m_direct_member` a/k/a `Value_set::m_direct_member`.
1143 * @param ARG_description
1144 * Description of the option as shown to the user in help text. Use 1+ sentence(s), starting with capital and
1145 * ending with punctuator. Indicate all key semantics, including special values, but (1) try to keep to one
1146 * sentence and line if possible; and (2) save extended discussion for the comment on the data member declaration
1147 * itself. Omit discussion of defaults; the default will be auto-printed and might change in code later.
1148 * Reminder: This is a brief summary -- not a `man` page equivalent... but a `man` page equivalent *should* be
1149 * in the actual code.
1150 * @param ARG_bool_validate_expr
1151 * In a fashion similar to `assert()` arg, an expression convertible to `bool` such that if and only if it
1152 * evaluates to `false` at the time of parsing a value for `ARG_m_value` from a config source, then the value
1153 * is considered invalid, causing parse failure for that config source. The expression shall assume that
1154 * the parsed value is in the variable of type `Value` named `val`. (If no further validation is needed, you
1155 * may simply pass in `true`.)
1156 */
1157#define FLOW_CFG_OPTION_SET_DECLARE_OPTION(ARG_m_value, ARG_description, ARG_bool_validate_expr) \
1158 FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, false)
1159
1160/**
1161 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION(), except the option is marked as *non-accumulating*, which
1162 * means that each time a config source (such as file) is parsed, this option's value is reset to default and then
1163 * only overwritten with a potential non-default value if explicitly specified in the config source. (Otherwise
1164 * the option is accumulating, hence whatever value is already stored in the `Value_set` is left unchanged unless
1165 * specified in the config source.)
1166 *
1167 * @note This may be useful, in particular, when used in flow::cfg::Config_manager in multi-file updates for special
1168 * options that implement conditionality. It does not make sense to accumulate such options' values from file to
1169 * file in a given update, so this `_NO_ACC` variant macro makes sense to use.
1170 *
1171 * @param ARG_m_value
1172 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1173 * @param ARG_description
1174 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1175 * @param ARG_bool_validate_expr
1176 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1177 */
1178#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC(ARG_m_value, ARG_description, ARG_bool_validate_expr) \
1179 FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, true)
1180
1181/**
1182 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION(), but with support for setting a value at a container subscript.
1183 *
1184 * It works as follows. Normally `ARG_m_value`, when stringified via `#` preprocessor trick, looks like a sequence of
1185 * `m_...` of identifiers joined with `.` and/or `->`. Call these *terms*. In this case, however, exactly one of the
1186 * terms -- but not the trailing term -- must end with the postfix `[...]`, where `...` may be 1 or more characters
1187 * excluding `]`. As in FLOW_CFG_OPTION_SET_DECLARE_OPTION(), the option name will be auto-computed based on
1188 * the stringification; but with one extra step at the end: `[...]` shall be replaced by the added macro
1189 * arg `ARG_key`, which must be `ostream<<`able.
1190 *
1191 * This can be used to fill out fixed-key-set containers of `struct`s concisely. Otherwise one would have to
1192 * tediously unroll the loop and manually provide the name via FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED().
1193 * Perhaps best shown by example:
1194 *
1195 * ~~~
1196 * // In Value_set:
1197 * constexpr size_t S_N_GRAND_PARENTS = 4;
1198 * struct Grand_parent { string m_name; bool m_male_else_female = false; };
1199 * array<Grand_parent, S_N_GRAND_PARENTS> m_grand_parents;
1200 * // When declaring options for Option_set<Value_set>:
1201 * for (size_t idx = 0; idx != Value_set::S_N_GRAND_PARENTS; ++idx)
1202 * {
1203 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_name, "The grandparent's name.", true, idx);
1204 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_male_else_female, "Their gender.", true, idx);
1205 * }
1206 * // Resulting config options:
1207 * "grand-parents.0.name"
1208 * "grand-parents.0.male-else-female"
1209 * "grand-parents.1.name"
1210 * "grand-parents.1.male-else-female"
1211 * "grand-parents.2.name"
1212 * "grand-parents.2.male-else-female"
1213 * "grand-parents.3.name"
1214 * "grand-parents.3.male-else-female"
1215 * // Example file:
1216 * [grand-parents.0]
1217 * name=Alice Eve
1218 * male-else-female=false
1219 * [grand-parents.1]
1220 * name=Ray Liotta
1221 * male-else-female=true
1222 * ...
1223 * ~~~
1224 *
1225 * Tip: In this example `idx` is a number; and the container is an `array`. However, it can be any container with
1226 * both keys and values; it just needs to always have the same key set after `Value_set` construction: `map`, `vector`,
1227 * etc. Accordingly the key can be anything `ostream<<`-able (e.g., `string` works). However -- behavior is undefined
1228 * if the value, when `ostream<<`ed, would not be accepted by boost.program_options as an option name postfix.
1229 * Informally: keep it to alphanumerics and underscores.
1230 *
1231 * @note Do not confuse this with the situation where *one* option is *itself* a `vector<T>`, where `T` is a scalar
1232 * (from boost.program_options' point of view). E.g., a `vector<int>` can simply be a single option, and
1233 * the user can supply as many or as few elements as they want, at runtime (in config file, one option per line).
1234 * For example the above `Value_set` can *itself* contain a `vector<>` in *each* `struct` in the array:
1235 *
1236 * ~~~
1237 * // In Value_set:
1238 * struct Grand_parent { ...; vector<int> m_kid_ages; };
1239 * // When declaring options for Option_set<Value_set>:
1240 * for (size_t idx = 0; idx != Value_set::S_N_GRAND_PARENTS; ++idx)
1241 * {
1242 * ...
1243 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_kid_ages, "Children's age list.", true, idx);
1244 * }
1245 * // Resulting config options:
1246 * ...
1247 * "grand-parents.0.kid-ages"
1248 * ...
1249 * "grand-parents.1.kid-ages"
1250 * ...
1251 * "grand-parents.2.kid-ages"
1252 * ...
1253 * "grand-parents.3.kid-ages"
1254 * // Example file:
1255 * [grand-parents.0]
1256 * name=Alice Eve
1257 * male-else-female=false
1258 * kid-ages=5
1259 * kid-ages=7
1260 * kid-ages=25
1261 * [grand-parents.1]
1262 * name=Ray Liotta
1263 * male-else-female=true
1264 * kid-ages=32
1265 * ...
1266 * ~~~
1267 *
1268 * @param ARG_m_value
1269 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1270 * @param ARG_description
1271 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1272 * @param ARG_bool_validate_expr
1273 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1274 * @param ARG_key
1275 * See above.
1276 */
1277#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(ARG_m_value, ARG_description, ARG_bool_validate_expr, ARG_key) \
1278 FLOW_UTIL_SEMICOLON_SAFE \
1279 ( \
1280 const ::std::string FLOW_CFG_SET_DECL_OPT_KEYED_name \
1281 = ::flow::cfg::value_set_member_id_to_opt_name_keyed(#ARG_m_value, ARG_key); \
1282 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1283 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_KEYED_name.c_str(), ARG_description, ARG_bool_validate_expr, false); \
1284 )
1285
1286/**
1287 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED_NO_ACC(), except the option is marked as *non-accumulating*
1288 * in the same sense as for FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC().
1289 *
1290 * @see FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() for a brief explanation of how non-accumulating options work.
1291 *
1292 * @param ARG_m_value
1293 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1294 * @param ARG_description
1295 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1296 * @param ARG_bool_validate_expr
1297 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1298 * @param ARG_key
1299 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED().
1300 */
1301#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED_NO_ACC(ARG_m_value, ARG_description, ARG_bool_validate_expr, ARG_key) \
1302 FLOW_UTIL_SEMICOLON_SAFE \
1303 ( \
1304 const ::std::string FLOW_CFG_SET_DECL_OPT_KEYED_name \
1305 = ::flow::cfg::value_set_member_id_to_opt_name_keyed(#ARG_m_value, ARG_key); \
1306 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1307 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_KEYED_name.c_str(), ARG_description, ARG_bool_validate_expr, true); \
1308 )
1309
1310/**
1311 * As of this writing is identical to either FLOW_CFG_OPTION_SET_DECLARE_OPTION() or
1312 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC(), depending on the value of `ARG_no_accumulation` argument.
1313 *
1314 * Rationale: Currently we don't necessarily expect this to be widely used directly in place of the aforementioned two
1315 * macros, but it seems reasonable to have a single macro with all various knobs given as parameters with certain
1316 * knob combinations potentially invoked via macro(s) based on this one. As of this
1317 * writing there is just the one extra knob, `ARG_no_accumulation`, but in the future we might expand to more
1318 * variations in which case more args would be added here.
1319 *
1320 * @param ARG_m_value
1321 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1322 * @param ARG_description
1323 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1324 * @param ARG_bool_validate_expr
1325 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1326 * @param ARG_no_accumulation
1327 * `bool` such that `false` causes FLOW_CFG_OPTION_SET_DECLARE_OPTION() behavior, while `true`
1328 * causes FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() behavior.
1329 */
1330#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, \
1331 ARG_no_accumulation) \
1332 FLOW_UTIL_SEMICOLON_SAFE \
1333 ( \
1334 const ::std::string FLOW_CFG_SET_DECL_OPT_name = ::flow::cfg::value_set_member_id_to_opt_name(#ARG_m_value); \
1335 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1336 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_name.c_str(), ARG_description, ARG_bool_validate_expr, \
1337 ARG_no_accumulation); \
1338 )
1339
1340/**
1341 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(), except the user must specify the option's string name
1342 * manually as an argument to the functional macro. (FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS() itself
1343 * is basically FLOW_CFG_OPTION_SET_DECLARE_OPTION() plus certain knobs which the former sets to commonly-used values
1344 * for concision.)
1345 *
1346 * This macro, which can be used directly but normally is not, is internally invoked (potentially indirectly) by any
1347 * other `FLOW_CFG_OPTION_SET_DECLARE_OPTION*()` macro. As such it is the essential core such macro.
1348 *
1349 * Informally: it is best to avoid its direct use, as it can break the auto-naming conventions maintained by
1350 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() and similar. That said a couple of use cases might be:
1351 * - FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED() uses it in its impl to add a convention for container subscripts.
1352 * - One can specify a legacy option-name alias. (E.g., one can FLOW_CFG_OPTION_SET_DECLARE_OPTION()
1353 * an option and then FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED() a manually-named option targeting
1354 * the same `Value_set` member. Then either the auto-generated name or the legacy name can be used in a config
1355 * source, until the latter is deprecated-out successfully in the field.)
1356 *
1357 * @param ARG_opt_name_c_str
1358 * The option's manually specified string name. Type: directly convertible to `const char*`; typically
1359 * a string literal.
1360 * @param ARG_m_value
1361 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1362 * @param ARG_description
1363 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1364 * @param ARG_bool_validate_expr
1365 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1366 * @param ARG_no_accumulation
1367 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS().
1368 */
1369#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED(ARG_m_value, ARG_opt_name_c_str, \
1370 ARG_description, ARG_bool_validate_expr, \
1371 ARG_no_accumulation) \
1372 FLOW_UTIL_SEMICOLON_SAFE \
1373 ( \
1374 char const * const FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str = ARG_opt_name_c_str; \
1375 /* Subtlety: This is only safe to use here synchronously. */ \
1376 const ::flow::util::String_view FLOW_CFG_SET_DECL_OPT_MANUAL_name_view(FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str); \
1377 const bool FLOW_CFG_SET_DECL_OPT_MANUAL_no_acc = ARG_no_accumulation; \
1378 switch (args.m_call_type) \
1379 { \
1380 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_PARSING_ROLE_OPT_TABLE: \
1381 { \
1382 using Value = decltype(args.m_args.m_fill_parsing_role_opt_table_args.m_values_candidate->ARG_m_value); \
1383 /* Subtlety: Copy input NUL-terminated char* into lambda string capture just in case they give us */ \
1384 /* not the usual `static` string literal as ARG_opt_name_c_str. */ \
1385 flow::Function<bool (const Value&)> FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func \
1386 = [FLOW_CFG_SET_DECL_OPT_MANUAL_name_std_str = std::string(FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str)] \
1387 ([[maybe_unused]] const Value& val) -> bool { return ARG_bool_validate_expr; }; \
1388 args.m_args.m_fill_parsing_role_opt_table_args.m_option_set \
1389 ->declare_option_for_parsing \
1390 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1391 &args.m_args.m_fill_parsing_role_opt_table_args.m_values_candidate->ARG_m_value, \
1392 /* Default is irrelevant if option accumulates from parse to parse. Pass null. */ \
1393 /* Default from Value_set() shall be in effect at construction time, but that's it. */ \
1394 /* However if it's set as a non-accumulating option via knob, then pass-through the default: */ \
1395 /* each parse via boost.program_options shall first reset option to that default; then if present */ \
1396 /* overwrite that default. Hence the value from any preceding parse is always forgotten. */ \
1397 FLOW_CFG_SET_DECL_OPT_MANUAL_no_acc \
1398 ? &args.m_args.m_fill_parsing_role_opt_table_args.m_values_default_no_acc->ARG_m_value \
1399 : nullptr, \
1400 ::std::move(FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func), \
1401 ::flow::util::String_view(#ARG_bool_validate_expr)); \
1402 break; \
1403 } \
1404 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE: \
1405 args.m_args.m_fill_output_help_role_opt_table_args.m_option_set \
1406 ->declare_option_for_help \
1407 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1408 args.m_args.m_fill_output_help_role_opt_table_args.m_values_default->ARG_m_value, \
1409 ARG_description); \
1410 break; \
1411 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE: \
1412 ::flow::cfg::Option_set_base::declare_option_for_output \
1413 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1414 args.m_args.m_fill_output_current_role_opt_table_args.m_target_opts, \
1415 args.m_args.m_fill_output_current_role_opt_table_args.m_values_default->ARG_m_value, \
1416 args.m_args.m_fill_output_current_role_opt_table_args.m_values_current->ARG_m_value, \
1417 ARG_description); \
1418 break; \
1419 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_COMPARE_PARSED_VALS: \
1420 args.m_args.m_compare_parsed_vals_args.m_option_set \
1421 ->scan_parsed_option \
1422 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1423 args.m_args.m_compare_parsed_vals_args.m_option_set->values().ARG_m_value); \
1424 break; \
1425 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_LOAD_VALS_AS_IF_PARSED: \
1426 args.m_args.m_load_val_as_if_parsed_args.m_option_set \
1427 ->load_option_value_as_if_parsed \
1428 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1429 &args.m_args.m_load_val_as_if_parsed_args.m_values_candidate->ARG_m_value, \
1430 args.m_args.m_load_val_as_if_parsed_args.m_values_to_load->ARG_m_value); \
1431 break; \
1432 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_VALIDATE_STORED_VALS: \
1433 { \
1434 /* Set up validator func similarly to above; see those comments. */ \
1435 using Value = decltype(args.m_args.m_validate_stored_vals_args.m_values_to_validate->ARG_m_value); \
1436 flow::Function<bool (const Value&)> FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func \
1437 = [FLOW_CFG_SET_DECL_OPT_MANUAL_name_std_str = std::string(FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str)] \
1438 ([[maybe_unused]] const Value& val) -> bool { return ARG_bool_validate_expr; }; \
1439 /* Throw if invalid; else no-op. */ \
1440 ::flow::cfg::Option_set_base::validate_parsed_option \
1441 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1442 args.m_args.m_validate_stored_vals_args.m_values_to_validate->ARG_m_value, \
1443 ::std::move(FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func), \
1444 ::flow::util::String_view(#ARG_bool_validate_expr)); \
1445 break; \
1446 } \
1447 /* No `default:` intentionally: most compilers should catch a missing enum value and warn. */ \
1448 } \
1449 ) // FLOW_UTIL_SEMICOLON_SAFE()
1450
1451// Template implementations.
1452
1453template<typename Value_set>
1455 Declare_options_func&& declare_opts_func_moved) :
1456 log::Log_context(logger_ptr, Flow_log_component::S_CFG),
1457 m_nickname(nickname),
1458 m_null(true),
1459 m_declare_opts_func(std::move(declare_opts_func_moved)),
1460 m_parsing(false)
1461{
1462 // Avoid INFO logging, in case they're only creating us to print some help and exit. Not essential info here.
1463 FLOW_LOG_TRACE("Option_set[" << *this << "]: Created with default values payload. Options table setup begins.");
1464
1465 // Refer to the doc headers for Opt_table and Declare_options_func to understand the next couple of calls.
1466
1467 // Load the *parsing-role* Opt_table m_opts_for_parsing. Its contents won't change after this.
1471 m_declare_opts_func(args);
1472 // m_null is now permanently true or false.
1473
1474 /* Load the *output-role* (help text sub-role) Opt_table. The defaults source is the current Value_set m_values
1475 * itself (since it was just loaded up with defaults by definition). It again won't change after this. */
1478 m_declare_opts_func(args);
1479
1480 FLOW_LOG_TRACE("Option_set[" << *this << "]: Options table setup finished. Null set? = [" << null() << "].");
1481} // Option_set::Option_set()
1482
1483template<typename Value_set>
1485{
1486 return m_null;
1487}
1488
1489template<typename Value_set>
1490const Value_set& Option_set<Value_set>::values() const
1491{
1492 return m_values;
1493}
1494
1495template<typename Value_set>
1497{
1498 return Mutable_values_ptr(new Values(values()));
1499}
1500
1501template<typename Value_set>
1503{
1504 return m_parsing ? &m_values_candidate : 0;
1505}
1506
1507template<typename Value_set>
1508void Option_set<Value_set>::values_to_ostream(std::ostream& os, const Values* values_or_null) const
1509{
1510 /* Provide more real estate than the default 80, since this is chiefly for value output, probably in logs.
1511 * And just put the description on the next line always. */
1512 constexpr unsigned int LINE_LENGTH = 1000;
1513 constexpr unsigned int DESC_LENGTH = LINE_LENGTH - 1;
1514
1515 Opt_table opts_for_output(LINE_LENGTH, DESC_LENGTH);
1516
1518 args.m_call_type = Declare_options_func_args::Call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE;
1520 = { &opts_for_output, &m_values_default,
1521 values_or_null ? values_or_null : &m_values };
1522 m_declare_opts_func(args);
1523
1524 os << opts_for_output; // Leverage boost.program_options to print the nicely formatted current+defaults+descriptions.
1525}
1526
1527template<typename Value_set>
1528void Option_set<Value_set>::log_values(util::String_view summary, const Value_set* values_or_null, log::Sev sev) const
1529{
1531 using std::flush;
1532
1533 String_ostream os;
1534 values_to_ostream(os.os(), values_or_null);
1535 os.os() << flush;
1536 FLOW_LOG_WITH_CHECKING(sev, "Option_set[" << *this << "]: Values payload [" << summary << "]:\n" << os.str());
1537}
1538
1539template<typename Value_set>
1540void Option_set<Value_set>::help_to_ostream(std::ostream& os) const
1541{
1542 os << m_opts_for_help; // Leverage boost.program_options to print the nicely formatted defaults+descriptions.
1543}
1544
1545template<typename Value_set>
1547{
1549 using std::flush;
1550
1551 String_ostream os;
1552 help_to_ostream(os.os());
1553 os.os() << flush;
1554 FLOW_LOG_WITH_CHECKING(sev, "Config usage [" << summary << "]:\n" << os.str());
1555}
1556
1557template<typename Value_set>
1558const boost::unordered_set<std::string>& Option_set<Value_set>::option_names() const
1559{
1560 return m_opt_names;
1561}
1562
1563template<typename Value_set>
1564template<typename Value>
1566 Value* target_value, const Value* value_default_if_no_acc,
1567 Function<bool (const Value& val)>&& validator_func_moved,
1568 util::String_view validator_cond_str_view)
1569{
1571 using opts::value;
1572 using std::string;
1573 using std::flush;
1574
1575 assert(target_value);
1576 string name(name_view);
1577
1578 m_opt_names.insert(name);
1579
1580 // Avoid INFO logging, in case they're only creating us to print some help and exit. Not essential info here.
1581 FLOW_LOG_TRACE("Option_set[" << *this << "]: Options table of parsing-role: "
1582 "Declaring parsing-capable option [" << name << "]; raw shallow size [" << sizeof(Value) << "].");
1583
1584 /* *Parsing-role* target is within m_values_candidate. No default: if not in config, it'll just be left untouched.
1585 * The validator function we create shall throw on validation failure, having been passed `const Value& val` by the
1586 * options engine. */
1587 const auto val_spec = value<Value>(target_value)
1588 ->notifier(throw_on_invalid_func(name_view, std::move(validator_func_moved),
1589 validator_cond_str_view));
1590 /* However: if non-accumulating mode is enabled then, in fact, set the value from Value_set()
1591 * as default_value(); so that starting to parse a config source (e.g., config file) shall always reset to default
1592 * first instead of accumulating from a previous parse (if any). This matters for a given parse only if this
1593 * option is not specified in that config source. */
1594 if (value_default_if_no_acc)
1595 {
1596 // Subtlety re. default_value(2 args): Same as in declare_option_for_help().
1597 String_ostream default_str_os;
1598 value_to_ostream(default_str_os.os(), *value_default_if_no_acc);
1599 default_str_os.os() << flush;
1600
1601 val_spec->default_value(*value_default_if_no_acc, default_str_os.str());
1602 }
1603
1604 m_opts_for_parsing.add_options()(name.c_str(), val_spec);
1605
1606 m_null = false;
1607} // Option_set::declare_option_for_parsing()
1608
1609template<typename Value>
1611 (util::String_view name_view,
1612 Function<bool (const Value& val)>&& validator_func_moved,
1613 util::String_view validator_cond_str_view) // Static.
1614{
1617 using std::string;
1618 using std::flush;
1619
1620 return [validator_func = std::move(validator_func_moved),
1621 validator_cond_str = string(validator_cond_str_view),
1622 name = string(name_view)]
1623 (const Value& val)
1624 {
1625 if (!(validator_func(val)))
1626 {
1627 String_ostream msg_os;
1628 msg_os.os() << "Option [" << name << "]: "
1629 "Validation failed; the following condition must hold: [" << validator_cond_str << "]. "
1630 "Option value `val` = [";
1631 value_to_ostream(msg_os.os(), val);
1632 msg_os.os() << "]." << flush;
1633 throw Runtime_error(msg_os.str());
1634 }
1635 };
1636} // Option_set_base::throw_on_invalid_func()
1637
1638template<typename Value_set>
1639template<typename Value>
1641 const Value& value_default, util::String_view description)
1642{
1644 using opts::value;
1645 using std::string;
1646 using std::flush;
1647
1648 assert(!description.empty());
1649
1650 /* Subtlety: The is also a ->default_value(1 arg) overload, which always uses ostream<< for output, but (as
1651 * elsewhere) run it through our possible output massaging machinery by providing the string to output. Hence use
1652 * the 2-arg version which takes an explicit string. @todo Could also implement a proxy type with an operator<<
1653 * ostream overload to make these call sites more elegant/concise. */
1654 String_ostream default_str_os;
1655 value_to_ostream(default_str_os.os(), value_default);
1656 default_str_os.os() << flush;
1657
1658 m_opts_for_help.add_options()
1659 (string(name).c_str(),
1660 // *Output-role* has no target; it does output, not input.
1661 value<Value>()
1662 ->default_value(value_default, default_str_os.str()),
1663 string(description).c_str());
1664
1665 // No need to log: logs are only interesting when setting up the parsing table. This'd just be verbose/redundant.
1666} // Option_set::declare_option_for_help()
1667
1668template<typename Value>
1670 const Value& value_default, const Value& current_value,
1671 util::String_view description) // Static.
1672{
1674 using opts::value;
1675 using std::string;
1676 using std::flush;
1677
1678 assert(!description.empty());
1679
1680 /* This is similar to declare_option_for_help() with 2 differences:
1681 * - The target is passed to us, *target_opts, as they will be printing that, and it depends on a current value
1682 * which changes over time, hence we can't just plant this in some data member the way we did with
1683 * m_opts_for_help.
1684 * - In addition to the default, add the current value into the output by massaging the description text.
1685 * We are printing a current value which will go into the description text here, while the default will
1686 * go into the default slot of the "usage" output to ostream. */
1687 String_ostream description_os;
1688
1689 // Visually match how boost.program_options prints the default value above us.
1690
1691 description_os.os() << " (";
1692 /* Always pass any user output of Value_set members through our possible specialization/overload around ostream<<,
1693 * as we may want to apply some us-specific massaging. */
1694 value_to_ostream(description_os.os(), current_value);
1695 description_os.os() << ") <==current | default==^\n"
1696 " " << description << flush;
1697
1698 // Subtlety re. default_value(2 args): Same as in declare_option_for_help().
1699 String_ostream default_str_os;
1700 value_to_ostream(default_str_os.os(), value_default);
1701 default_str_os.os() << flush;
1702
1703 target_opts->add_options()
1704 (string(name).c_str(),
1705 // *Output-role* has no target; it does output, not input.
1706 value<Value>()
1707 ->default_value(value_default, default_str_os.str()),
1708 description_os.str().c_str());
1709
1710 // No need to log: logs are only interesting when setting up the parsing table. This'd just be verbose/redundant.
1711} // Option_set_base::declare_option_for_output()
1712
1713template<typename Value_set>
1714template<typename Value>
1715void Option_set<Value_set>::scan_parsed_option(util::String_view name_view, const Value& canonical_value)
1716{
1717 using boost::any_cast;
1718 using std::string;
1719 string name(name_view);
1720
1721 /* In this mode we are basically to check whether the value just parsed for `name`, which is in
1722 * m_iterable_values_candidate[name], is actually *different* from the current canonical value, which is
1723 * in canonical_value. If so we are to leave it there; if not, we are to remove it,
1724 * thus maintaining the invariant wherein m_iterable_values_candidate mirrors m_values_candidate, except for those
1725 * options whose values have not changed. */
1726
1727 const auto it = m_iterable_values_candidate.find(name);
1728 if (it == m_iterable_values_candidate.end())
1729 {
1730 return; // Option was not parsed.
1731 }
1732 // else
1733
1734 const auto& new_val = any_cast<const Value&>(it->second);
1735 const auto& old_val = canonical_value;
1736
1737 if (old_val == new_val)
1738 {
1739 m_iterable_values_candidate.erase(name);
1740 }
1741 return;
1742} // Option_set::scan_parsed_option()
1743
1744template<typename Value_set>
1745template<typename Value>
1747 Value* target_value,
1748 const Value& source_value)
1749{
1750 using std::string;
1751 string name(name_view);
1752
1753 assert(target_value);
1754 assert(m_parsing);
1755
1756 /* Getting here is hairy to understand, but our task is simple: Modify *this as-if
1757 * *target_value -- which is a member of m_values_candidate -- was parsed from some config source, yielding
1758 * in valid value source_value. We must update our state (given that m_parsing is true) accordingly. */
1759
1760 // This is the obvious part: set the thing inside m_values_candidate.
1761 *target_value = source_value;
1762
1763 // Now mirror it in m_iterable_values_candidate to maintain that invariant.
1764 m_iterable_values_candidate[name] = source_value;
1765
1766 /* That's it. Just picture it as if <name_view contents>=<encoding of source_value> was in a config file,
1767 * and parse_config_file() just did a store()/notify() of that line alone and hence updated
1768 * m_iterable_values_candidate accordingly. What we did above equals that; it's just much simpler to do
1769 * than actual parsing, since we have the value given to us directly in source_value. */
1770}
1771
1772template<typename Value>
1774 Function<bool (const Value& val)>&& validator_func_moved,
1775 util::String_view validator_cond_str_view) // Static.
1776{
1777 const auto validate_or_throw_func = throw_on_invalid_func(name_view, std::move(validator_func_moved),
1778 validator_cond_str_view);
1779 validate_or_throw_func(value);
1780} // Option_set_base::validate_parsed_option()
1781
1782template<typename Value_set>
1784 (const fs::path& cfg_path, bool allow_unregistered, bool* success_or_null,
1785 const boost::unordered_set<std::string>& allowed_unregistered_opts_or_empty)
1786{
1789 using util::key_exists;
1790 using opts::notify;
1791 using opts::store;
1792 using opts::variables_map;
1793 using boost::system::system_category;
1794 using fs::ifstream;
1795 using std::exception;
1796 using std::string;
1797
1798 // If they invoked us in non-throwing mode, call selves recursively in throwing mode and catch it.
1799 if (success_or_null)
1800 {
1801 try
1802 {
1803 parse_config_file(cfg_path, allow_unregistered, 0, allowed_unregistered_opts_or_empty);
1804 }
1805 catch (const exception& exc)
1806 {
1807 // We already logged on exception; be quiet.
1808 *success_or_null = false;
1809 return;
1810 }
1811 *success_or_null = true;
1812 return;
1813 }
1814 // else: Throw an exception on error. Perf not a real concern.
1815
1816 if (!m_parsing)
1817 {
1818 m_parsing = true;
1819 FLOW_LOG_INFO("Option_set[" << *this << "]: State CANONICAL: Request to parse file [" << cfg_path << "]; "
1820 "entering state PARSING. Initial candidate values payload initialized from current canonical "
1821 "values payload. Details follow (TRACE log level).");
1822
1823 assert(m_iterable_values_candidate.empty());
1824 }
1825 else
1826 {
1827 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Request to parse file [" << cfg_path << "]; "
1828 "continuing by parsing this next config source. "
1829 "The values payload going into it is as it was after the previous config source. "
1830 "Details follow (TRACE log level).");
1831 }
1832
1833 log_values("pre-parsing candidate config", &m_values_candidate, log::Sev::S_TRACE);
1834
1835 ifstream ifs(cfg_path);
1836 if (!ifs)
1837 {
1838 const Error_code sys_err_code(errno, system_category());
1840
1841 throw Runtime_error(sys_err_code, ostream_op_string("Could not open file [", cfg_path, "]."));
1842 }
1843 // else
1844
1845 /* parse_() can throw on invalid option values and so on! In case it managed to touch m_values_candidate -- save it.
1846 * Our doc header(s) guarantee config payload is untouched unless parsing succeeds.
1847 * @todo This may not be necessary. boost.program_options is solid, but there's an uncommon (for Boost) looseness
1848 * to the documentation, including about error handling, so we don't find it trustworthy enough not to do this and
1849 * stay maintainable. Still consider revisiting this and not catching the exception and not worrying about
1850 * backing this up -- if that's really how it works. */
1851 const auto values_candidate_backup = m_values_candidate;
1852 opts::parsed_options parsed_options(&m_opts_for_parsing);
1853 try
1854 {
1855 parsed_options = opts::parse_config_file(ifs, m_opts_for_parsing, allow_unregistered);
1856 }
1857 catch (const exception& exc)
1858 {
1859 m_values_candidate = values_candidate_backup;
1860 // m_iterable_values_candidate is set below, so it hasn't been touched from this config source yet.
1861
1862 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1863 "Parsing file [" << cfg_path << "]: Exception occurred; parse failed. Values payload untouched. "
1864 "Message = [" << exc.what() << "].");
1865 throw;
1866 }
1867 // Got here? No problem parsing (but we haven't validated). First a detour:
1868
1869 /* If we were told to allow at least some (possibly all) unregistered options:
1870 * Log (at appropriate level) the unregistered options; log only the name part of the tokens for each (ignoring
1871 * any values); and fail if an un-approved unknown option shows up. */
1872 if (allow_unregistered)
1873 {
1874 for (const auto& option : parsed_options.options)
1875 {
1876 if (option.unregistered)
1877 {
1878 assert(!option.original_tokens.empty());
1879 const string opt_name = option.original_tokens.front();
1880
1881 if (allowed_unregistered_opts_or_empty.empty())
1882 {
1883 // Do not use INFO level; this can get verbose.
1884 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Ignoring unregistered option named "
1885 "[" << opt_name << "]. We were not given an approved-list. Perhaps this file contains "
1886 "other option sets, or it has to do with forward/backward-compatibility.");
1887 }
1888 else
1889 {
1890 if (!key_exists(allowed_unregistered_opts_or_empty, opt_name))
1891 {
1892 // Same deal as above.
1893 m_values_candidate = values_candidate_backup;
1894
1895 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: Unregistered option named "
1896 "[" << opt_name << "] is not approved; parse failed. Values payload untouched.");
1897 throw Runtime_error(ostream_op_string("Unregistered option named [", opt_name,
1898 "] is not approved; parse failed."));
1899 }
1900 // else
1901
1902 // Do not use INFO level; this can get verbose.
1903 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Ignoring unregistered option named "
1904 "[" << opt_name << "]; it is in the approved-list. Perhaps this file contains "
1905 "other option sets.");
1906 } // if (!allowed_unregistered_opts_or_empty.empty())
1907 } // if (option.unregistered)
1908 } // for (option : parsed_options.options)
1909 // We don't care about them further.
1910 }
1911 // else { Apparently there were no unknown options encountered, as it didn't throw when parsing. }
1912
1913 // Store into a variables_map (a glorified map<string, boost::any>) and m_values_candidate.
1914
1915 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Parsed general format successfully. "
1916 "Will now parse each option's value.");
1917
1918 /* As explained in ###Rationale### in Iterable_values doc header, we use a fresh opts::variables_map as an
1919 * intermediary and load the results from that into our Iterable_values, overwriting any values already there
1920 * that were indeed set during this parse_*(). In other words, for every configured option N, it either does not
1921 * exist in m_iterable_values_candidate (in which case the value is still at its canonical m_values value), or it does
1922 * as m_iterable_values_candidate[N] (in which case it has been parsed at least once in a previous config source's
1923 * parse step *and* is different from m_values). Then after store() it will either be created or changed or remain
1924 * unchanged. In the latter case we should remove it to maintain the invariant, wherein only keys for
1925 * changed-from-canonical options exist. */
1926 variables_map fresh_iterable_values;
1927 try
1928 {
1929 store(parsed_options, fresh_iterable_values);
1930 }
1931 catch (const exception& exc)
1932 {
1933 // Same deal as above.
1934 m_values_candidate = values_candidate_backup;
1935
1936 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1937 "Parsed file [" << cfg_path << "]: Exception occurred; individual option parsing failed. "
1938 "Values payload untouched. Message = [" << exc.what() << "].");
1939 throw;
1940 }
1941
1942 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Parsed each individual option successfully. "
1943 "Will now further validate each option value logically and store on success.");
1944
1945 /* notify() will finish things by loading m_values_candidate members and validating them.
1946 * notify() can throw, particularly due to our own custom validating notifiers (see declare_option_for_parsing()). */
1947 try
1948 {
1949 notify(fresh_iterable_values);
1950 }
1951 catch (const exception& exc)
1952 {
1953 // Same deal as above.
1954 m_values_candidate = values_candidate_backup;
1955
1956 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1957 "Parsed file [" << cfg_path << "]: Exception occurred; individual logical validation failed. "
1958 "Values payload untouched. Message = [" << exc.what() << "].");
1959 throw;
1960 }
1961
1962 // As noted above transfer from the clean variables_map into our Iterable_values.
1963 for (const auto& pair : fresh_iterable_values)
1964 {
1965 const auto& name = pair.first;
1966 const auto& var_value = pair.second.value(); // boost.any.
1967
1968 assert((!key_exists(m_iterable_values_candidate, name) ||
1969 (m_iterable_values_candidate[name].type() == var_value.type())));
1970
1971 m_iterable_values_candidate[name] = var_value; // Copying boost.any copies the payload it holds.
1972 }
1973
1974 // This is the part that removes each option such that it was just parsed, but the new value makes it == canonical.
1976 args.m_call_type = Declare_options_func_args::Call_type::S_COMPARE_PARSED_VALS;
1978 m_declare_opts_func(args);
1979
1980 /* m_values_candidate and its iterable mirror m_iterable_values_candidate have been filled in!
1981 * By definition the former contains the pre-parsing values, plus whatever was in the config source overwriting
1982 * zero or more of those. m_iterable_values_candidate fully mirrors by including only those values different from
1983 * m_values_candidate. This fact may be used in canonicalize_candidate(). (For now, though, we don't care about
1984 * m_iterable_values_candidate outside of keeping it updated after each parse.) */
1985
1986 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Validate/store succeded. "
1987 "Further validation deferred until attempt to enter state CANONICAL. "
1988 "Updated values payload details follow.");
1989 log_values("post-parsing candidate config", &m_values_candidate, log::Sev::S_TRACE);
1990} // Option_set::parse_config_file()
1991
1992template<typename Value_set>
1994{
1995 if (!m_parsing)
1996 {
1997 m_parsing = true;
1998 FLOW_LOG_INFO("Option_set[" << *this << "]: State CANONICAL: Request to load values from source struct "
1999 "[" << &src_values << "]; entering state PARSING. "
2000 "Any preceding canonical state will be ignored. Details follow (TRACE log level).");
2001
2002 assert(m_iterable_values_candidate.empty());
2003 }
2004 else
2005 {
2006 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Request to load values from source struct "
2007 "[" << &src_values << "]; continuing by parsing this next config source. "
2008 "Any current candidate values payload will be overwritten entirely. "
2009 "Details follow (TRACE log level).");
2010 }
2011
2012 /* The best way to explain our task is... to me (ygoldfel), it's like parse_config_file() of an imaginary file
2013 * that would have contained every possible option, with values src_values. We need to do the stuff to end up
2014 * in the same state. (We already updated m_parsing if needed.) Of course it's a lot easier to just load the values
2015 * as opposed to really parsing them (which can fail validation and other difficulties). However we still need
2016 * to update the same m_* state that the parsing would have. Namely:
2017 * - m_values_candidate should equal src_values (as far as each individual option goes).
2018 * - m_iterable_values_candidate must be updated accordingly (see its doc header). */
2019
2020 /* This work-horse will indeed update m_values_candidate as needed and mirror each value in
2021 * m_iterable_values_candidate[name]. In parse_config_file() that is what occurs through the step where
2022 * m_iterable_values_candidate is loaded from the variable_map generated by store()/notify(). */
2024 args.m_call_type = Declare_options_func_args::Call_type::S_LOAD_VALS_AS_IF_PARSED;
2025 args.m_args.m_load_val_as_if_parsed_args = { this, &m_values_candidate, &src_values };
2026 m_declare_opts_func(args);
2027
2028 /* Now, m_iterable_values_candidate contains every (config-enabled) member of a Values struct.
2029 * Note, in particular, that this overwrites anything that may have been parsed from other config sources
2030 * until that point (as our log messages indicate) -- as all of src_values was copied over.
2031 *
2032 * All we need now is to ensure m_iterable_values_candidate stores *only* the values that were changed
2033 * compared to the canonical m_values. For that we just do the same thing parse_config_file() would at this stage. */
2034 args.m_call_type = Declare_options_func_args::Call_type::S_COMPARE_PARSED_VALS;
2036 m_declare_opts_func(args);
2037
2038 log_values("post-loading (perhaps baseline or rewound) candidate config", &m_values_candidate, log::Sev::S_TRACE);
2039} // Option_set::parse_direct_values()
2040
2041template<typename Value_set>
2043{
2044 using util::key_exists;
2045
2046 assert(m_parsing);
2047
2048 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Entering state CANONICAL. "
2049 "Values payload finalized. Details follow (TRACE log level).");
2050 log_values("entering-CANONICAL-state finalized config", &m_values_candidate, log::Sev::S_TRACE);
2051
2052 /* Before finalizing m_values from m_values_candidate let's examine changes.
2053 * m_iterable_values_candidate maintains the invariant of only include those options whose values have changed
2054 * vs. m_values. So this is trivial. */
2055
2056 for (const auto& pair : m_iterable_values_candidate)
2057 {
2058 const auto& name = pair.first;
2059 FLOW_LOG_INFO("Option_set[" << *this << "]: Detected change in option named [" << name << "].");
2060 }
2061
2062 if (change_detected)
2063 {
2064 *change_detected = (!m_iterable_values_candidate.empty());
2065 }
2066
2067 // All is right with the world.
2068
2069 m_parsing = false;
2070 m_values = m_values_candidate;
2071 m_iterable_values_candidate.clear();
2072} // Option_set::canonicalize_candidate()
2073
2074template<typename Value_set>
2076{
2077 if (m_parsing)
2078 {
2079 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Rejecting candidate payload built; "
2080 "reverting to state CANONICAL.");
2081 m_parsing = false;
2082 m_iterable_values_candidate.clear();
2083 // m_values remains unchanged. m_values_candidate becomes meaningless (see its doc header: m_parsing == false now).
2084 }
2085}
2086
2087template<typename Value_set>
2088void Option_set<Value_set>::validate_values(bool* success_or_null) const
2089{
2090 validate_values(values(), success_or_null);
2091}
2092
2093template<typename Value_set>
2095{
2096 const auto candidate = values_candidate();
2097 assert(candidate && "Do not invoke validate_values_candidate() in CANONICAL state; only while PARSING.");
2098 validate_values(*candidate, success_or_null);
2099}
2100
2101template<typename Value_set>
2102void Option_set<Value_set>::validate_values(log::Logger* logger_ptr, const Values& values_to_validate,
2103 const Declare_options_func& declare_opts_func,
2104 bool* success_or_null) // Static.
2105{
2106 using std::exception;
2107
2108 // If they invoked us in non-throwing mode, call selves recursively in throwing mode and catch it.
2109 if (success_or_null)
2110 {
2111 try
2112 {
2113 validate_values(logger_ptr, values_to_validate, declare_opts_func);
2114 }
2115 catch (const exception& exc)
2116 {
2117 // We already logged on exception; be quiet.
2118 *success_or_null = false;
2119 return;
2120 }
2121 *success_or_null = true;
2122 return;
2123 }
2124 // else: Throw an exception on error. Perf not a real concern.
2125
2126 FLOW_LOG_SET_CONTEXT(logger_ptr, Flow_log_component::S_CFG);
2127
2129 args.m_call_type = Declare_options_func_args::Call_type::S_VALIDATE_STORED_VALS;
2130 args.m_args.m_validate_stored_vals_args.m_values_to_validate = &values_to_validate;
2131 try
2132 {
2133 /* Simple as that! This will invoke FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each option; and that will
2134 * simply call validate_parsed_option() with all the needed info like name, current value, validation condition
2135 * string, and most importantly the validator function created from the validation Boolean expression user
2136 * passed to FLOW_CFG_OPTION_SET_DECLARE_OPTION(). validate_parsed_option() will throw when invoked on an invalid
2137 * value. Catch it to log it, as promised, but then re-throw it, as if it fell through. */
2138 declare_opts_func(args);
2139 }
2140 catch (const exception& exc)
2141 {
2142 FLOW_LOG_WARNING("Stand-alone validation check failed: Details: [" << exc.what() << "].");
2143 throw;
2144 }
2145} // void Option_set::validate_values()
2146
2147template<typename Value_set>
2148void Option_set<Value_set>::validate_values(const Values& values_to_validate, bool* success_or_null) const
2149{
2150 validate_values(get_logger(), values_to_validate, m_declare_opts_func, success_or_null);
2151}
2152
2153template<typename Value_set>
2154std::ostream& operator<<(std::ostream& os, const Option_set<Value_set>& val)
2155{
2156 return os << '[' << val.m_nickname << "]@" << &val;
2157}
2158
2159template<typename Value>
2160void value_to_ostream(std::ostream& os, const Value& val)
2161{
2162 os << val;
2163}
2164
2165template<typename Rep, typename Period>
2166void value_to_ostream(std::ostream& os, const boost::chrono::duration<Rep, Period>& val)
2167{
2169 using util::String_view;
2170 using boost::chrono::ceil;
2171 using boost::chrono::nanoseconds;
2172 using boost::chrono::microseconds;
2173 using boost::chrono::milliseconds;
2174 using boost::chrono::seconds;
2175 using boost::chrono::minutes;
2176 using boost::chrono::hours;
2177 using std::string;
2178
2179 using raw_ticks_t = uint64_t; // Should be big enough for anything.
2180
2181 // Nanoseconds should be the highest precision we use anywhere. It won't compile if this can lose precision.
2182 nanoseconds val_ns(val);
2183
2184 // Deal with positives only... why not?
2185 raw_ticks_t raw_abs_ticks = (val_ns.count() < 0) ? (-val_ns.count()) : val_ns.count();
2186
2187 /* We can of course simply `os << val_ns` -- and it'll print something like "<...> ns" which is correct.
2188 * Instead we'd like to use more convenient units -- but without losing precision due to conversion; so convert
2189 * up to the most coarse units possible. First, though, take care of the special case 0: */
2190 if (raw_abs_ticks == 0)
2191 {
2192 os << seconds::zero(); // Seconds are a nice default. It's strange to say "0 hours" or what-not.
2193 return;
2194 }
2195 // else
2196
2197 // Then keep trying this until can no longer try a coarser unit without losing precision.
2198 const auto try_next_unit = [&](auto pre_division_dur, raw_ticks_t divide_by) -> bool
2199 {
2200 if ((raw_abs_ticks % divide_by) == 0)
2201 {
2202 // Dividing current raw_abs_ticks by divide_by does not lose precision; so at worst can output post-division dur.
2203 raw_abs_ticks /= divide_by;
2204 return false;
2205 }
2206 // else: It does lose precision, so we must stop here and print it in pre-division units.
2207 os << ostream_op_string(pre_division_dur);
2208 return true;
2209 };
2210
2211 if (!(try_next_unit(val_ns, 1000)
2212 || try_next_unit(ceil<microseconds>(val), 1000)
2213 || try_next_unit(ceil<milliseconds>(val), 1000)
2214 || try_next_unit(ceil<seconds>(val), 60)
2215 || try_next_unit(ceil<minutes>(val), 60)))
2216 {
2217 os << ceil<hours>(val);
2218 }
2219} // value_to_ostream()
2220
2221template<typename Element>
2222void value_to_ostream(std::ostream& os, const std::vector<Element>& val)
2223{
2224 for (size_t idx = 0; idx != val.size(); ++idx)
2225 {
2226 os << '[';
2227 value_to_ostream<Element>(os, val[idx]);
2228 os << ']';
2229 if (idx != (val.size() - 1))
2230 {
2231 os << ' ';
2232 }
2233 }
2234} // value_to_ostream()
2235
2236template<typename Key>
2237std::string value_set_member_id_to_opt_name_keyed(util::String_view member_id, const Key& key)
2238{
2239 using util::String_view;
2241 using boost::regex;
2242 using boost::regex_match;
2243 using boost::smatch;
2244 using std::string;
2245
2246 // Keep in harmony with value_set_member_id_to_opt_name() of course,
2247
2248 /* <anything, long as possible>[<replaced stuff>]<.blah... or ->blah... though we don't enforce it here>
2249 * =>
2250 * <anything, long as possible>.<`key`><.blah... or ->blah... though we don't enforce it here> */
2251
2252 smatch matched_groups;
2253 /* @todo Should I be imbuing the regex with std::locale::classic() or something?
2254 * @todo Can probably make it work directly on String_view member_id; seems to conflict with `smatch`; but
2255 * probably it can be figured out. Perf at this level is hardly of high import so not worrying. */
2256 const string member_id_str(member_id);
2257#ifndef NDEBUG
2258 const bool matched_ok =
2259#endif
2260 regex_match(member_id_str, matched_groups, VALUE_SET_MEMBER_ID_TO_OPT_NAME_KEYED_REGEX);
2261 assert(matched_ok && "Member identifier must look like: <longest possible stuff>[<key to replace>]<the rest>");
2262
2263 constexpr char INDEX_SEP_BEFORE('.');
2264
2265 /* Don't post-process or pre-process a single value_set_member_id_to_opt_name() result.
2266 * The replacer in that function shouldn't run across the substituted-in `key` (which could contain, e.g., "m_..." --
2267 * which would get incorrectly replaced by just "..."); and running the replacer followed by making the
2268 * [...]-by-dot-key substution is at least aesthetically perf-wasteful (those characters get replaced anyway later).
2269 * Anyway, all that aside, the following is easily defensible as clearly not having any such issues. */
2270 return ostream_op_string
2271 (value_set_member_id_to_opt_name(String_view(&*matched_groups[1].first, matched_groups[1].length())),
2272 INDEX_SEP_BEFORE,
2273 key, // ostream<< it.
2274 value_set_member_id_to_opt_name(String_view(&*matched_groups[2].first, matched_groups[2].length())));
2275} // value_set_member_id_to_opt_name_keyed()
2276
2277} // namespace flow::cfg
Un-templated base for Option_set.
Definition: option_set.hpp:34
static void declare_option_for_output(util::String_view name, opts::options_description *target_opts, const Value &value_default, const Value &current_value, util::String_view description)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
static Function< void(const Value &val)> throw_on_invalid_func(util::String_view name, Function< bool(const Value &val)> &&validator_func_moved, util::String_view validator_cond_str)
Returns a function that wraps a Value->Boolean validator function, as passed to declare_option_for_pa...
Declare_options_func_call_type
Internal-use type to use with Option_set::Declare_options_func callback.
Definition: option_set.hpp:47
@ S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE
Internal use only through macro.
@ S_FILL_PARSING_ROLE_OPT_TABLE
Internal use only through macro.
static void validate_parsed_option(util::String_view name, const Value &value, Function< bool(const Value &val)> &&validator_func_moved, util::String_view validator_cond_str)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
opts::options_description Opt_table
Short-hand for boost.program_options config options description, each of which is used for parsing an...
Definition: option_set.hpp:216
The core config-parsing facility, which builds parsing/comparison/output capabilities on top of a giv...
Definition: option_set.hpp:422
typename Values::Ptr Mutable_values_ptr
Short-hand for ref-counted pointer to a mutable Values (config payload storable in an Option_set).
Definition: option_set.hpp:442
bool m_null
See null().
const boost::unordered_set< std::string > & option_names() const
Returns set of all option names declared by the option-declaring function passed to the constructor.
std::map< std::string, boost::any > Iterable_values
Similar to a boost.program_options parsing results variables_map, which conceptually mirrors the resu...
Opt_table m_opts_for_parsing
The parsing-role Opt_table (see extensive explanation in Opt_table doc header).
Iterable_values m_iterable_values_candidate
Structure mirroring m_values_candidate, where the values are the parsing-enabled members of m_values_...
Mutable_values_ptr mutable_values_copy() const
Convenience method that heap-allocates a copy of the internally stored values() and wraps in a ref-co...
void validate_values(bool *success_or_null=0) const
Validates the current contents of values() using the validators *this Option_set<Value_set> is config...
void parse_config_file(const fs::path &cfg_path, bool allow_unregistered, bool *success_or_null=0, const boost::unordered_set< std::string > &allowed_unregistered_opts_or_empty={})
Enters into (from CANONICAL state) or continues in PARSING state by parsing the config source in the ...
const std::string m_nickname
See nickname ctor arg.
boost::unordered_set< std::string > m_opt_names
See option_names().
void log_help(util::String_view summary, log::Sev sev=log::Sev::S_INFO) const
Logs a multi-line help message using help_to_ostream().
Values m_values
See values().
void load_option_value_as_if_parsed(util::String_view name, Value *target_value, const Value &source_value)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
const Values & values() const
Externally immutable internally stored canonical (current) config values as last constructed or parse...
Declare_options_func m_declare_opts_func
The Opt_table-filling, or m_iterable_values_candidate-scanning, callback passed to the constructor an...
typename Values::Const_ptr Values_ptr
Short-hand for ref-counted pointer to an immutable Value_set (config payload storable in an Option_se...
Definition: option_set.hpp:439
void canonicalize_candidate(bool *change_detected=0)
In PARSING state enters CANONICAL state, finalizing values() from values_candidate().
void declare_option_for_parsing(util::String_view name, Value *target_value, const Value *value_default_if_no_acc, Function< bool(const Value &val)> &&validator_func_moved, util::String_view validator_cond_str)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
Opt_table m_opts_for_help
The output-role Opt_table, help-text sub-role (see extensive explanation in Opt_table doc header).
Value_set Values
Short-hand for the template parameter type Value_set.
Definition: option_set.hpp:430
Option_set(log::Logger *logger_ptr, util::String_view nickname, Declare_options_func &&declare_opts_func_moved)
Constructs an option set in CANONICAL state with a default-valued values() payload and options declar...
bool m_parsing
See values_candidate() – true if and only if that returns non-null (PARSING state; else CANONICAL sta...
void reject_candidate()
In PARSING state, returns to CANONICAL state, as if no parse attempts have occurred.
void log_values(util::String_view summary, const Values *values_or_null=0, log::Sev sev=log::Sev::S_INFO) const
Logs the given values payload using values_to_ostream().
bool null() const
Return true if and only if the option-declaring function passed to the constructor declared no option...
void declare_option_for_help(util::String_view name, const Value &value_default, util::String_view description)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
void values_to_ostream(std::ostream &os, const Values *values_or_null=0) const
Writes a multi-line user-suitable representation of the current values in a Values object,...
const Values * values_candidate() const
Returns null in CANONICAL state; or in PARSING state a pointer to the not-yet-canonical values after ...
void help_to_ostream(std::ostream &os) const
Prints a multi-line help message about the set of options that *this can parse.
Values m_values_candidate
See values_candidate(). When that returns null (m_parsing == false) this value is meaningless.
void validate_values_candidate(bool *success_or_null=0) const
Equivalent to validate_values(success_or_null) but validates *values_candidate() instead of values().
Values m_values_default
Copy of values() when it is first constructed; i.e., the defaults.
void parse_direct_values(const Values &src_values)
Enters into (from CANONICAL state) or continues in PARSING state by simply setting *values_candidate(...
void scan_parsed_option(util::String_view name, const Value &canonical_value)
Internal-through-macro helper function; the user shall not call this directly but only through FLOW_C...
An std::runtime_error (which is an std::exception) that stores an Error_code.
Definition: error.hpp:49
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1619
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Similar to ostringstream but allows fast read-only access directly into the std::string being written...
std::ostream & os()
Access to stream that will write to owned string.
#define FLOW_ERROR_SYS_ERROR_LOG_WARNING()
Logs a warning about the (often errno-based or from a library) error code in sys_err_code.
Definition: error.hpp:269
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:197
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:152
#define FLOW_LOG_WITH_CHECKING(ARG_sev, ARG_stream_fragment)
Logs a message of the specified severity into flow::log::Logger *get_logger() with flow::log::Compone...
Definition: log.hpp:489
#define FLOW_LOG_SET_CONTEXT(ARG_logger_ptr, ARG_component_payload)
For the rest of the block within which this macro is instantiated, causes all FLOW_LOG_....
Definition: log.hpp:405
#define FLOW_LOG_TRACE(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:227
Flow module that facilitates configuring modules, such as applications and APIs, via statically and/o...
Definition: cfg_fwd.hpp:112
void value_to_ostream(std::ostream &os, const Value &val)
Serializes a value of type Value to the given ostream suitably for output in Option_set-related outpu...
std::string value_set_member_id_to_opt_name(util::String_view member_id)
Utility, used by FLOW_CFG_OPTION_SET_DECLARE_OPTION() internally but made available as a public API i...
Definition: option_set.cpp:38
std::string value_set_member_id_to_opt_name_keyed(util::String_view member_id, const Key &key)
Similar to value_set_member_id_to_opt_name() but used by FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED() i...
const boost::regex VALUE_SET_MEMBER_ID_TO_OPT_NAME_KEYED_REGEX
An internal constant for value_set_member_id_to_opt_name_keyed().
std::ostream & operator<<(std::ostream &os, const Option_set< Value_set > &val)
Serializes (briefly) an Option_set to a standard output stream.
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:224
@ S_TRACE
Message indicates any condition that may occur with great frequency (thus verbose if logged).
@ S_INFO
Message indicates a not-"bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
bool key_exists(const Container &container, const typename Container::key_type &key)
Returns true if and only if the given key is present at least once in the given associative container...
Definition: util.hpp:276
std::string ostream_op_string(T const &... ostream_args)
Equivalent to ostream_op_to_string() but returns a new string by value instead of writing to the call...
Definition: util.hpp:356
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
boost::system::error_code Error_code
Short-hand for a boost.system error code (which basically encapsulates an integer/enum error code and...
Definition: common.hpp:502
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:632
Internal-use structure to use with Declare_options_func callback.
Definition: option_set.hpp:456
Option_set * m_option_set
m_option_set->m_opts_for_parsing shall be filled out.
Definition: option_set.hpp:480
const Values * m_values_current
The current values loaded into *m_target_opts (e.g., via description text) will originate here.
Definition: option_set.hpp:514
struct flow::cfg::Option_set::Declare_options_func_args::@0::@1 m_fill_parsing_role_opt_table_args
Corresponds to Call_type::S_FILL_PARSING_ROLE_OPT_TABLE.
struct flow::cfg::Option_set::Declare_options_func_args::@0::@2 m_fill_output_help_role_opt_table_args
Corresponds to Call_type::S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE.
struct flow::cfg::Option_set::Declare_options_func_args::@0::@3 m_fill_output_current_role_opt_table_args
Corresponds to Call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE.
struct flow::cfg::Option_set::Declare_options_func_args::@0::@5 m_load_val_as_if_parsed_args
Corresponds to Call_type::S_LOAD_VALS_AS_IF_PARSED.
struct flow::cfg::Option_set::Declare_options_func_args::@0::@4 m_compare_parsed_vals_args
Corresponds to Call_type::S_COMPARE_PARSED_VALS.
const Values * m_values_to_validate
Each validator-enabled (via a Declare_options_func) member in this structure shall be validated.
Definition: option_set.hpp:549
const Values * m_values_default
m_option_set->m_values_default: defaults loaded into Option_set::m_opts_for_help will originate here.
Definition: option_set.hpp:500
const Values * m_values_to_load
Values shall be copied from this external Value_set.
Definition: option_set.hpp:542
opts::options_description * m_target_opts
m_target_opts shall be filled out based on the defaults earlier saved into m_values_default and curre...
Definition: option_set.hpp:510
Call_type m_call_type
Why Declare_options_func is being called.
Definition: option_set.hpp:465
Values * m_values_candidate
m_option_set->m_values_candidate, the parsing-in-progress Value_set filled out whenever parsing using...
Definition: option_set.hpp:485
const Values * m_values_default_no_acc
m_option_set->m_values_default: the defaults loaded into m_values_candidate at the top of each parse,...
Definition: option_set.hpp:491
union flow::cfg::Option_set::Declare_options_func_args::@0 m_args
The args to pass in depending on m_call_type.
struct flow::cfg::Option_set::Declare_options_func_args::@0::@6 m_validate_stored_vals_args
Corresponds to Call_type::S_VALIDATE_STORED_VALS.