Flow 2.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 cycles 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. #Call_type and Option_set_base::Declare_options_func_call_type could be eliminated:
473 * For example #m_fill_parsing_role_opt_table_args could have a named (currently it's anonymous)
474 * type `struct Fill_parsing_role_opt_table`, used as the `variant` selector value for that call-type. So
475 * when calling the #Declare_options_func callback while desiring that operation, we'd load the variant
476 * with a `struct Fill_parsing_role_opt_table`; and FLOW_CFG_OPTION_SET_DECLARE_OPTION() would
477 * get a non-null `get_if<Fill_parsing_role_opt_table>()` result, indicating that's the desired op; if not
478 * then it would try the others one by one (conceptually a `switch()` statement -- much like today). A tiny
479 * bit slower, but in this context it's totally fine. (Or could use an explicit `variant` index and have
480 * an actual `switch()`.)
481 */
482 union
483 {
484 /// Corresponds to Call_type::S_FILL_PARSING_ROLE_OPT_TABLE.
485 struct
486 {
487 /// `m_option_set->m_opts_for_parsing` shall be filled out.
489 /**
490 * `m_option_set->m_values_candidate`, the parsing-in-progress `Value_set` filled out whenever parsing using
491 * the completed Option_set::m_opts_for_parsing.
492 */
494 /**
495 * `m_option_set->m_values_default`: the defaults loaded into #m_values_candidate at the top of each
496 * parse, for any option specified as non-accumulating. (Any members whose corresponding option is a regular
497 * accumulating one is, in this context, ignored; instead the preceding parse's final value is kept unchanged.)
498 */
501
502 /// Corresponds to Call_type::S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE.
503 struct
504 {
505 /// `m_option_set->m_opts_for_help` shall be filled out.
507 /// `m_option_set->m_values_default`: defaults loaded into `Option_set::m_opts_for_help` will originate here.
510 /* (For the @todo to turn union into variant<>: Could here have
511 * `struct Fill_output_help_role_opt_table { ... };` and have a
512 * `variant<Fill_output_help_role_opt_table, ...the others...>`; and then to determine which one has been
513 * loaded into args: get_if<Fill_output_help_role_opt_table>(); if non-null then that is it; else if null => try
514 * get_if<...each of the other ones>...() (first non-null one = the one).) */
515
516 /// Corresponds to Call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE.
517 struct
518 {
519 /**
520 * #m_target_opts shall be filled out based on the defaults earlier saved into #m_values_default and
521 * current values in #m_values_current.
522 */
523 opts::options_description* m_target_opts;
524 /// `m_option_set->m_values_default`: the defaults loaded into `*m_target_opts` will originate here.
526 /// The current values loaded into `*m_target_opts` (e.g., via description text) will originate here.
529
530 /// Corresponds to Call_type::S_COMPARE_PARSED_VALS.
531 struct
532 {
533 /**
534 * Canonical values in `m_option_set->values()` shall be compared with parsed values in
535 * `m_option_set->m_iterable_values_candidate`.
536 */
539
540 /// Corresponds to Call_type::S_LOAD_VALS_AS_IF_PARSED.
541 struct
542 {
543 /**
544 * Values in `*(m_option_set->values_candidate())` (and related) shall be loaded from #m_values_to_load, as-if
545 * parsed from some config source.
546 */
548
549 /**
550 * `m_option_set->m_values_candidate`, the structure to load from #m_values_to_load.
551 */
553
554 /// Values shall be copied from this external `Value_set`.
557
558 /// Corresponds to Call_type::S_VALIDATE_STORED_VALS.
559 struct
560 {
561 /// Each validator-enabled (via a #Declare_options_func) member in this structure shall be validated.
565 }; // struct Declare_options_func_args
566
567 /**
568 * Short-hand for the ever-important callback passed to the main Option_set constructor. The user of the class
569 * need not understand the meanings of the args, because the FLOW_CFG_OPTION_SET_DECLARE_OPTION() macro will
570 * take care of using them properly.
571 *
572 * @internal
573 *
574 * The idea is that the body of a user-provided callback of this type will consist of invoking
575 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each parseable option, in some consistent order, and that macro (which
576 * we control) will invoke handling of that particular option -- whose stored type can be anything and different from
577 * the other options', meaning we cannot iterate through them at runtime (nor is it possible at compile time without
578 * some kind of insane meta-programming). So, depending on what arguments we pass to this callback, the macro will --
579 * upon performing an operation only a macro can do (namely `#ARG_whatever` and the like) -- forward those args
580 * to a function templated on the type of the option's stored value, namely one of `declare_option_*()`,
581 * scan_parsed_option(), load_option_value_as_if_parsed(), or validate_parsed_option(). That function will then
582 * perform the needed handling for that particular option.
583 *
584 * See Declare_options_func_args for the possible args' meanings.
585 */
587
588 // Constructors/destructor.
589
590 /**
591 * Constructs an option set in CANONICAL state with a default-valued values() payload and options declared
592 * by synchronously invoking the callback `declare_opts_func()`. See below for details on the latter.
593 *
594 * Post-condition: `values()` is equal to `Values{}`; values_candidate() is null (so the state is initially
595 * CANONICAL). (Therefore `Value_set` no-args ctor must by definition be written so as to initialize all its
596 * relevant members to their defaults. Recall `Value_set` is a template parameter type with certain requirements.)
597 *
598 * Use `parse_*()` (once per config source) and `canonicalize_candidate()` (once) to move to PARSING state and back
599 * to CANONICAL state respectively.
600 *
601 * ### Declaring options via `declare_opts_func()` callback ###
602 * `declare_opts_func` above denotes an internally saved callback `move()`d from the corresponding arg
603 * `declare_opts_func_moved`. `declare_opts_func()` is also invoked synchronously
604 * from within various relevant output APIs; their doc headers
605 * mention it. (E.g., invoking stream output `os << *this` will call `declare_opts_func()`.)
606 *
607 * Informally, the callback must declare every parseable (and therefore stream-printable-semantic-description-having)
608 * option, linking it to some data member within a target `Values` whose address is passed to the callback as an
609 * arg; in a consistent order; and as many times through the lifetime of `*this` (including during this ctor) as
610 * `*this` deems necessary. Use FLOW_CFG_OPTION_SET_DECLARE_OPTION() to describe all the options.
611 * Formally, it must follow the following exact rules:
612 * - Its signature right above the `{ body }` must name its args *exactly* as shown in the value of the
613 * #Declare_options_func alias.
614 * - Its body must execute N>=1 invocations of the macro
615 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() -- see its doc header -- each corresponding to a distinct
616 * data member of `Values`, always in the same order and the same N. Clues as to how this works:
617 * - The naming of the args is so specific in order to make the macro minimally concise to use.
618 * - Macro machinery will auto-determine the main name of the option (as seen in config sources like files)
619 * based on the naming of the `m_` member.
620 * - The macro will call a public API that is not to be called directly, passing in various
621 * data just mentioned, plus the text description passed to it (if relevant) and the option name passed to it.
622 *
623 * @param logger_ptr
624 * Logger to use for subsequently logging.
625 * @param nickname
626 * Brief string used for logging subsequently.
627 * @param declare_opts_func_moved
628 * See above.
629 */
630 explicit Option_set(log::Logger* logger_ptr, util::String_view nickname,
631 Declare_options_func&& declare_opts_func_moved);
632
633 // Methods.
634
635 /**
636 * Externally immutable internally stored canonical (current) config values as last constructed or parsed,
637 * whichever happened more recently.
638 *
639 * @see mutable_values_copy() to get a copy.
640 *
641 * @return See above.
642 */
643 const Values& values() const;
644
645 /**
646 * Convenience method that heap-allocates a copy of the internally stored `values()` and wraps in a ref-counted
647 * handle suitable for speedy passing around the rest of the application.
648 *
649 * @return See above.
650 */
652
653 /**
654 * Returns null in CANONICAL state; or in PARSING state a pointer to the not-yet-canonical values after the last
655 * successful `parse_*()` API call.
656 *
657 * Rationale: It is supplied publicly in case the caller wans to log it or something; or perhaps to check
658 * for current state (CANONICAL if and only it returns null).
659 *
660 * @return See above.
661 */
662 const Values* values_candidate() const;
663
664 /**
665 * Validates the current contents of values() using the validators `*this` `Option_set<Value_set>` is configured
666 * to use via constructor. If at least one option
667 * is invalid according to a validator declared by a FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocation in
668 * `declare_opts_func()`, then this function shall indicate failure; else success.
669 *
670 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
671 * On success simply returns or sets `*success_or_null` to `true` respectively. In the exception case the message
672 * will indicate every reasonable detail about the option value that went wrong. This info is logged regardless.
673 *
674 * Informally, the use case driving the presence of this overload is discussed in the Validation section of our
675 * class doc header; see that. To restate: If you want to stringently ensure the defaults are themselves valid,
676 * simply invoke this right after construction, at which point values() are by definition at their defaults.
677 *
678 * @param success_or_null
679 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
680 */
681 void validate_values(bool* success_or_null = nullptr) const;
682
683 /**
684 * Validates an arbitrary `Value_set`, as parseable by *an* `Option_set<Value_set>`, according to the
685 * given option-registering function suitable for `Option_set<Value_set>` constructor. If at least one option
686 * is invalid according to a validator declared by a FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocation in
687 * `declare_opts_func()`, then this function shall indicate failure; else success.
688 *
689 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
690 * On success simply returns or sets `*success_or_null` to `true` respectively. In the exception case the message
691 * will indicate every reasonable detail about the option value that went wrong. This info is logged regardless.
692 *
693 * Use this, at least, if you've filled out a `Value_set` through manual assignment or some other source, rather
694 * than parsing through an Option_set -- and don't even *use* an Option_set, as you don't parse from config --
695 * but still want to check it for validity.
696 *
697 * You can also use the non-`static` overload to reuse the #Declare_options_func passed to an existing
698 * parsing-capable `Option_set<Value_set>`.
699 *
700 * @param logger_ptr
701 * Logger to use for subsequently logging.
702 * @param values_to_validate
703 * The values in this structure shall be validated in the same way as they are upon parsing
704 * by a hypothetical `Option_set<Value_set>` into whose ctor the same `declare_opts_func` were passed.
705 * @param declare_opts_func
706 * See Option_set constructor.
707 * @param success_or_null
708 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
709 */
710 static void validate_values(log::Logger* logger_ptr,
711 const Values& values_to_validate, const Declare_options_func& declare_opts_func,
712 bool* success_or_null = nullptr);
713
714 /**
715 * Validates an arbitrary `Value_set`, using the same validators `*this` `Option_set<Value_set>` is configured to use
716 * when parsing config sources. This essentially means using the `static` overload and passing to it
717 * `declare_opts_func` equal to the one earlier passed by the user to `*this` constructor. The success/failure
718 * semantics are identical to the other overload's (see that doc header).
719 *
720 * You can also use the `static` overload, if you aren't at all parsing from config sources but still want to
721 * validate.
722 *
723 * @param values_to_validate
724 * The values in this structure shall be validated in the same way as they are upon `this->parse_*()`ing from
725 * a config source.
726 * @param success_or_null
727 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
728 */
729 void validate_values(const Values& values_to_validate, bool* success_or_null = nullptr) const;
730
731 /**
732 * Equivalent to `validate_values(success_or_null)` but validates `*values_candidate()` instead of values().
733 * Behavior is undefined (assertion may trip) if not in PARSING mode currently (i.e., values_candidate() is null).
734 *
735 * Informally, the use case driving the presence of this overload is discussed in the Validation section of our
736 * class doc header; see that. To restate: If you want to ensure no invalid defaults or baseline values have
737 * "gotten through" in the current PARSING state, then invoke this -- particularly just ahead of the decision to
738 * either canonicalize_candidate() or reject_candidate().
739 *
740 * @param success_or_null
741 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
742 */
743 void validate_values_candidate(bool* success_or_null = nullptr) const;
744
745 /**
746 * Writes a multi-line user-suitable representation of the current values in a #Values object, at some point
747 * perhaps initialized or parsed by `*this`, to the given stream. This should typically be preceded by
748 * a newline but not followed by one, unless one desires a blank line there.
749 *
750 * @param os
751 * Stream to which to serialize.
752 * @param values_or_null
753 * Values to serialize; if null then we act as-if it's `&(values())`.
754 */
755 void values_to_ostream(std::ostream& os, const Values* values_or_null = nullptr) const;
756
757 /**
758 * Logs the given values payload using values_to_ostream().
759 *
760 * @param values_or_null
761 * See values_to_ostream().
762 * @param summary
763 * Brief summary of what this payload represents.
764 * @param sev
765 * Severity to use for the log message.
766 */
767 void log_values(util::String_view summary,
768 const Values* values_or_null = nullptr, log::Sev sev = log::Sev::S_INFO) const;
769
770 /**
771 * Prints a multi-line help message about the set of options that `*this` can parse. This should typically
772 * be preceded by a newline but not followed by one, unless one desires a blank line there.
773 *
774 * @param os
775 * Stream to which to serialize.
776 */
777 void help_to_ostream(std::ostream& os) const;
778
779 /**
780 * Logs a multi-line help message using help_to_ostream().
781 *
782 * @param summary
783 * Brief summary of the help message.
784 * @param sev
785 * Severity to use for the log message.
786 */
787 void log_help(util::String_view summary, log::Sev sev = log::Sev::S_INFO) const;
788
789 /**
790 * Enters into (from CANONICAL state) or continues in PARSING state by parsing the config source in the form of
791 * the given file in the file-system. On success values_candidate() is updated; on failure it is untouched.
792 *
793 * On failure throws an exception, if `success_or_null` is null; otherwise set `*success_or_null` to `false`.
794 * On success simply returns or sets `*success_or_null` to `true` respectively. Information is logged regardless.
795 *
796 * A failure may occur due to invalid contents in the config source. A failure may occur due to a validator
797 * condition failed (see FLOW_CFG_OPTION_SET_DECLARE_OPTION()).
798 *
799 * However: the latter applies only to settings actually specified in `cfg_path` file. Any default or baseline or
800 * otherwise-previously-set, but not overridden in `cfg_path`, values are not validated -- neither before nor after
801 * scanning `cfg_path`. You are, however, free to do so by next (or previously, or both) invoking
802 * validate_values_candidate(). (If "previously," and not yet in PARSING state, then use validate_values().)
803 * See Validation in class doc header for discussion.
804 *
805 * @see canonicalize_candidate() which will finalize the values_candidate() constructed so far.
806 * @see reject_candidate() which will snap back to CANONICAL state rejecting any successful parsing done.
807 * In particular it would make sense in many cases to do this if parse_config_file() indicates failure.
808 * @param cfg_path
809 * Path to parse.
810 * @param allow_unregistered
811 * If `true`, if an unknown option is encountered it may be allowed (not considered a failure),
812 * subject to `allowed_unregistered_opts_or_empty`, though an INFO
813 * message is still logged for each; if `false` it is an error like any other illegal config setting.
814 * One reason for `true` is if another `Option_set<>` will be parsed from the same config source.
815 * Another is if there could be forward- or backward-compatibility concerns.
816 * @param allowed_unregistered_opts_or_empty
817 * Meaningful only if `allow_unregistered == true`, this is the list of all option names (compared
818 * case-sensitively) that will not cause a validation error; or empty to allow *all* unknown options.
819 * @param success_or_null
820 * If null exceptions mark failure; otherwise the pointed-to value shall indicate success or failure.
821 */
822 void parse_config_file(const fs::path& cfg_path, bool allow_unregistered,
823 bool* success_or_null = nullptr,
824 const boost::unordered_set<std::string>& allowed_unregistered_opts_or_empty = {});
825
826 /**
827 * Enters into (from CANONICAL state) or continues in PARSING state by simply setting `*values_candidate()`
828 * to equal the #Values payload given as an argument. Typically precedes other `parse_*()` calls such as
829 * parse_config_file(). If already in PARSING state, note that any changes accumulated in `*values_candidate()`
830 * so far will be overwritten entirely.
831 *
832 * The values in `src_values` are not checked for validity according to the validators configured
833 * in the FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocations in `declare_opts_func()` passed to ctor.
834 * You are, however, free to do so by next invoking validate_values_candidate().
835 * See Validation in class doc header for discussion.
836 *
837 * ### Rationale ###
838 * This is useful, particularly, when one plans to repeatedly apply updates to one `*this`, but a certain *baseline*
839 * state is desired before each update. Consider the example of an Option_set that stores dynamically changeable
840 * values. Suppose each update consists only of a single `parse_config_file(F)` call, where `F` is some file that
841 * might get changed at various times to deliver dynamic updates. Then consider this series:
842 * -# Initial Option_set construction. End state: `values() == Values{}` (default).
843 * -# First update occurs: `parse_config_file(F)`, followed by canonicalize_candidate().
844 * End state: `values()` == defaults + changes in file `F` at time 1.
845 * -# Second update occurs: `parse_config_file(F)`, followed by canonicalize_candidate().
846 * End state: `values()` == defaults + changes in file `F` at time 1 +
847 * changes in file `F` at time 2.
848 * -# (etc.)
849 *
850 * In this case values() is incrementally changed by each dynamic update, as the file `F` keeps changing.
851 * E.g., if at time 1 it contained only option A, and at time 2 it contained only option B, then values()
852 * would contain both time-1 option A value and time-2 option B value -- even though the file `F` at
853 * time 2 contains only the latter. This *might* be what you want, but since it's fully incremental, there are
854 * some usability landmines. Mainly: If one looks at `F` contents at any given time, they can't know what the
855 * resulting state would be; it depends on what updates preceded it.
856 *
857 * To resolve this, one can save a *baseline* state of values() by copy; and then apply it via this
858 * parse_direct_values() call before parsing the file in each dynamic update. The baseline state could just be
859 * defaults (`Values{}`), or it could come from some special "baseline" config file that is not `F` which one knows
860 * to never change. (Such a file could also typically store static config managed by a separate Option_set.)
861 *
862 * So then the sequence might become not parse_config_file(), canonicalize_candidate(), parse_config_file(),
863 * canonicalize_candidate(), ...; but rather:
864 * -# Baseline parse: `parse_config_file(B)`, canonicalize_candidate().
865 * -# Save values() copy into `Values baseline`.
866 * -# Update 0: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
867 * -# Update 1: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
868 * -# Update 2: `parse_direct_values(baseline)`, `parse_config_file(F)`, canonicalize_candidate().
869 * -# ...
870 * (The first `parse_direct_values(baseline)` here is a no-op but included for clarity/symmetry, as usually one
871 * would just do the same thing for each update.)
872 *
873 * It is also sometimes desirable to "rewind" the state of values_candidate() (by first memorizing it, then
874 * parse_config_file() or similar, then if some value in the resulting value set indicates the file should not
875 * apply after all, parse_direct_values() to "undo." Config_manager uses it when its multi-source feature is
876 * engaged -- `commit = false`.)
877 *
878 * @param src_values
879 * The values set loaded into `*values_candidate()`, as-if parsed from some config source.
880 */
881 void parse_direct_values(const Values& src_values);
882
883 /**
884 * In PARSING state enters CANONICAL state, finalizing values() from values_candidate().
885 * Any cumulative changes are INFO-logged on a per-changed-option basis; and if at least one option's value indeed
886 * changed then `*change_detected` is set to `true` (else `false`). (Leave the arg null, if you do not care.)
887 *
888 * @note Individual option validation should be done via the validation condition argument to
889 * FLOW_CFG_OPTION_SET_DECLARE_OPTION(). However, it may be necessary to perform a check for internal
890 * consistency among the final values. The proper time to do this is just before calling
891 * canonicalize_candidate(). If the final check fails, typically one would instead call
892 * reject_candidate().
893 *
894 * @param change_detected
895 * If null, ignored; otherwise `*change_detected` is set to `true` if a setting changed; else `false`.
896 */
897 void canonicalize_candidate(bool* change_detected = nullptr);
898
899 /**
900 * In PARSING state, returns to CANONICAL state, as if no parse attempts have occurred. In CANONICAL state, a no-op.
901 * Calling this is typically a good idea when a `parse_*()` attempt indicates failure.
902 */
903 void reject_candidate();
904
905 /**
906 * Internal-through-macro helper function; the user shall not call this directly but only through
907 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
908 *
909 * @internal
910 * Loads an entry into #m_opts_for_parsing which will enable the parsing into `*target_value` from config sources
911 * such as config files.
912 * @endinternal
913 *
914 * @tparam Value
915 * Type of the value inside a #Values object. It must be reasonably copyable.
916 * @param name
917 * Main option name: as specified by the user in a config source.
918 * As of this writing FLOW_CFG_OPTION_SET_DECLARE_OPTION() uses macro magic to automatically form this
919 * from the identifier name within a #Values object (e.g., `Value_set::m_cool_option` => "cool-option").
920 * @param target_value
921 * When deserializing a value from a config source, the bits shall be written there.
922 * This must point inside `m_values_candidate`.
923 * @param value_default_if_no_acc
924 * Usually -- with regular (accumulating) options -- null; otherwise pointer to the default value for
925 * `*target_value` (as from `Values{}`), inside `m_values_default`. In the latter case (non-null) this
926 * indicates this is an option marked by the user as non-accumulating
927 * (see FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() and similar), meaning each time a config source
928 * (e.g., a file) is parsed `*target_value` is first reset to this default; then overwritten with the value in
929 * the config source if, else left at the default. An accumulating option in the latter case would instead
930 * keep its existing value already in `*target_value`.
931 * @param validator_func_moved
932 * Function F, such that F(V) shall be called from `parse_*()` when this option's successfully parsed value V
933 * is being validated before final storage in `m_values_candidate`. Return `false` if the validation
934 * shall fail; otherwise return `true`.
935 * @param validator_cond_str
936 * String containing the Boolean code that `validator_func_moved()` would need to evaluate to `true` to pass
937 * validation. As of this writing FLOW_CFG_OPTION_SET_DECLARE_OPTION() uses macro magic to automatically form
938 * this from a Boolean expression fragment.
939 */
940 template<typename Value>
941 void declare_option_for_parsing(util::String_view name, Value* target_value, const Value* value_default_if_no_acc,
942 Function<bool (const Value& val)>&& validator_func_moved,
943 util::String_view validator_cond_str);
944
945 /**
946 * Internal-through-macro helper function; the user shall not call this directly but only through
947 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
948 *
949 * @internal
950 * Loads an entry into #m_opts_for_help which will enable the proper help text to appear for that option
951 * in help_to_ostream() or log_help().
952 * @endinternal
953 *
954 * @tparam Value
955 * Type of the value inside a #Values object. It must be reasonably copyable; and it must be supported by
956 * some version (including specialization(s) and overload(s)) of value_to_ostream().
957 * @param name
958 * See declare_option_for_parsing().
959 * @param value_default
960 * Default value to show to the user.
961 * @param description
962 * The description text to show to the user.
963 */
964 template<typename Value>
965 void declare_option_for_help(util::String_view name, const Value& value_default, util::String_view description);
966
967 /**
968 * Internal-through-macro helper function; the user shall not call this directly but only through
969 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
970 *
971 * @internal
972 * Scans a canonical (current) value in #m_values, comparing it to the (possibly) parsed one in
973 * #m_iterable_values_candidate, erasing it from the latter if and only if it is unchanged.
974 * @endinternal
975 *
976 * @tparam Value
977 * Type of the value inside a #Values object. It must have a meaningful `==` operation.
978 * @param name
979 * See declare_option_for_parsing().
980 * @param canonical_value
981 * Current value in `m_values`.
982 */
983 template<typename Value>
984 void scan_parsed_option(util::String_view name, const Value& canonical_value);
985
986 /**
987 * Internal-through-macro helper function; the user shall not call this directly but only through
988 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() (see Option_set main constructor doc header).
989 *
990 * @internal
991 * To be called only when #m_parsing is `true`, sets a value in #m_values_candidate to equal one
992 * in an external #Values object; and mirrors this in #m_iterable_values_candidate to maintain its invariant.
993 * As a result, it is as-if that value was parsed from some config source such as a file.
994 * Note that, as with parsing from actual config sources like files, one must still scan_parsed_option()
995 * at some point to slim down #m_iterable_values_candidate to store merely the changed options.
996 * @endinternal
997 *
998 * @tparam Value
999 * Type of the value inside a #Values object. It must have a meaningful `=` operation.
1000 * @param name
1001 * See declare_option_for_parsing().
1002 * @param target_value
1003 * Target value inside `m_values_candidate`.
1004 * @param source_value
1005 * Value to load into `*target_value` (as-if it was parsed from a config source).
1006 */
1007 template<typename Value>
1008 void load_option_value_as_if_parsed(util::String_view name, Value* target_value, const Value& source_value);
1009
1010 /**
1011 * Return `true` if and only if the option-declaring function passed to the constructor declared no options.
1012 * This value is always the same for a given `*this`.
1013 *
1014 * ### Rationale ###
1015 * While likely of little value when the user instantiates an Option_set directly, this can be useful in
1016 * generic meta-programming, wherein multiple `Option_set`s might be instantiated at compile time, but the
1017 * coder doesn't know how many while coding. For example Config_manager uses null() to bypass confusingly-logging
1018 * parse_config_file() calls on empty Option_set objects, such as if a static config set has no dynamic config
1019 * counterpart.
1020 *
1021 * @return See above.
1022 */
1023 bool null() const;
1024
1025 /**
1026 * Returns set of all option names declared by the option-declaring function passed to the constructor.
1027 * This can be useful to supply to parse_config_file(), for example, when parsing another Option_set from the
1028 * same file: then other options can be passed to that function as not causing a parse error if encountered;
1029 * hence 2 or more `Option_set`s can parse_config_file() the same file despite having disjoint option name sets --
1030 * yet totally extraneous options in none of the `Option_set`s would cause an error as desired.
1031 *
1032 * This returns the same value (and reference) each time it is called.
1033 *
1034 * @return See above.
1035 */
1036 const boost::unordered_set<std::string>& option_names() const;
1037
1038 // Data.
1039
1040 /// See `nickname` ctor arg.
1041 const std::string m_nickname;
1042
1043private:
1044 // Types.
1045
1046 /**
1047 * Similar to a boost.program_options parsing results `variables_map`, which conceptually mirrors the results of a
1048 * parsing operation in the target `Values` structure,
1049 * mapping `string` to variable-type values. As explained in the doc header for
1050 * Option_set::Opt_table, a parsing operation takes in a config source (config file, etc.)
1051 * and a *parsing-role* `Opt_table`; and outputs a `Values` and an object of the present type.
1052 * `Values` is the main point Option_set exists; it's what the application accesses, via simple
1053 * `->m_*` access of various values, to observe the parsed config -- so why do we need `Iterable_values`?
1054 *
1055 * The reason we use this is: we need to be able to iterate through all the options that were found in a
1056 * parsing pass, at least to detect changes as needed at least for dynamic option features (triggering
1057 * on-changed hooks, etc.). (Requiring a manual `operator==(Value_set, Value_set)` or similar is too onerous
1058 * and error-prone for the implementor of the `Value_set`, as every `->m_` would require manual mention.)
1059 *
1060 * A `variables_map` is classically filled via the aforementioned parsing operation `opts::store()`;
1061 * and is itself an `std::map`. Hence one can copy all the pairs from that into an #Iterable_values; and
1062 * an #Iterable_values can be manually pre-filled with pre-parse values from `Values` for easy
1063 * comparison after the `store()` (and other such algorithms). Note, also, it is deep-copyable, as long as
1064 * all types stored within are deep-copyable.
1065 *
1066 * ### Rationale ###
1067 * - Why not simply use `opts::variables_map`? It is after all very close to what this is. Answer: I (ygoldfel)
1068 * started that way, but its API is a bit hairy, and it's scary to have to depend on how boost.program_options
1069 * will act when `variables_map` isn't empty before the parsing begins, for example (e.g., `store()` docs say
1070 * it will NOT overwrite any "non-defaulted" value -- having to rely on such nuances doesn't seem great).
1071 * It is safer, if slower (but we don't care), to treat a fresh `variables_map` that's `store()`d into as an
1072 * intermediary.
1073 * - Why not `unordered_map`? It's faster after all. Answer: An alphabetical iteration order seems nice when
1074 * (e.g.) iterating over the keys to show differences in values to humans.
1075 */
1076 using Iterable_values = std::map<std::string, boost::any>;
1077
1078 // Data.
1079
1080 /// See null().
1082
1083 /// See option_names().
1084 boost::unordered_set<std::string> m_opt_names;
1085
1086 /**
1087 * The #Opt_table-filling, or #m_iterable_values_candidate-scanning, callback passed to the constructor and invoked
1088 * with various input args (see #Declare_options_func doc header). Depending on those args it will either fill
1089 * `Opt_table` in various roles (see #Opt_table doc header), or it will scan the values just loaded into
1090 * #m_iterable_values_candidate (e.g., for equality to the values in canonical #m_values).
1091 */
1093
1094 /// See values().
1096
1097 /// Copy of values() when it is first constructed; i.e., the defaults.
1099
1100 /// See values_candidate() -- `true` if and only if that returns non-null (PARSING state; else CANONICAL state).
1102
1103 /// See values_candidate(). When that returns null (`m_parsing == false`) this value is meaningless.
1105
1106 /**
1107 * Structure mirroring #m_values_candidate, where the values are the parsing-enabled members of #m_values_candidate,
1108 * and each value's key is it main config option name; if an option has not changed between #m_values and
1109 * #m_values_candidate (according to its `==` operator), then that option's key is omitted. In particular
1110 * it is `.empty() == true` when in CANONICAL mode (`m_parsing == false`).
1111 */
1113
1114 /**
1115 * The *parsing-role* #Opt_table (see extensive explanation in #Opt_table doc header). Briefly: it's used
1116 * to actually load `*m_values_candidate` -- and ultimately #m_values -- with values parsed from config sources such
1117 * as files. This must not change after the first `parse_*()` API begins execution.
1118 */
1120
1121 /**
1122 * The *output-role* #Opt_table, help-text sub-role (see extensive explanation in #Opt_table doc header).
1123 * Briefly: it's used to output help text to output streams for user's convenience, in fact simply by piping it
1124 * to an `ostream` via `<<`. This should not change after the first `parse_*()` API begins execution (lest the
1125 * user become confused).
1126 */
1128}; // class Option_set
1129
1130// Free functions: in *_fwd.hpp.
1131
1132// Macros.
1133
1134/**
1135 * Macro the user must use, as prescribed in the flow::cfg::Option_set constructor doc header discussing
1136 * `declare_opts_func` in their flow::cfg::Option_set::Declare_options_func callback, when declaring each individual
1137 * config option as linked to a given data member of their config-bearing `struct` used as the `Value_set` in
1138 * `Option_set<Value_set>`.
1139 *
1140 * @note The resulting option name, as found in config sources such as config files, will be auto-computed from
1141 * the string `#ARG_m_value`. So if `ARG_m_value` is `m_cool_thing.m_cool_duration`, then the option name
1142 * might be computed to be, say, `"cool-thing.cool-duration"`, with a config file line:
1143 * `"cool-thing.cool-duration=2 milliseconds"`.
1144 * @note It may be desirable to *not* auto-name a given option; a known use case for this is legacy option names,
1145 * if renaming is difficult in the field. Use FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED() if indeed
1146 * you want to custom-name your option.
1147 *
1148 * @param ARG_m_value
1149 * Supposing `Value_set` is the template arg to `Option_set`, and `Value_set V` is being registered,
1150 * then `ARG_m_value` is such that `V.ARG_m_value` is the scalar into which the particular config option shall
1151 * be deserialized. It shall be a scalar data member name, optionally within a compound data member, which is
1152 * itself optionally within a compound, and so forth, within `Value_set V`. Each optional compounding may
1153 * be via simple object composition (`.`) or pointer dereference (`->`).
1154 * Examples: `m_direct_member`, `m_group_obj.m_indirect_member`,
1155 * `m_group.m_group1->m_pointed_group->m_cool_scalar`. Reminder: each of these must be accessible within
1156 * a `Value_set` via `.` compounding. E.g.: `V.m_direct_member` a/k/a `Value_set::m_direct_member`.
1157 * @param ARG_description
1158 * Description of the option as shown to the user in help text. Use 1+ sentence(s), starting with capital and
1159 * ending with punctuator. Indicate all key semantics, including special values, but (1) try to keep to one
1160 * sentence and line if possible; and (2) save extended discussion for the comment on the data member declaration
1161 * itself. Omit discussion of defaults; the default will be auto-printed and might change in code later.
1162 * Reminder: This is a brief summary -- not a `man` page equivalent... but a `man` page equivalent *should* be
1163 * in the actual code.
1164 * @param ARG_bool_validate_expr
1165 * In a fashion similar to `assert()` arg, an expression convertible to `bool` such that if and only if it
1166 * evaluates to `false` at the time of parsing a value for `ARG_m_value` from a config source, then the value
1167 * is considered invalid, causing parse failure for that config source. The expression shall assume that
1168 * the parsed value is in the variable of type `Value` named `val`. (If no further validation is needed, you
1169 * may simply pass in `true`.)
1170 */
1171#define FLOW_CFG_OPTION_SET_DECLARE_OPTION(ARG_m_value, ARG_description, ARG_bool_validate_expr) \
1172 FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, false)
1173
1174/**
1175 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION(), except the option is marked as *non-accumulating*, which
1176 * means that each time a config source (such as file) is parsed, this option's value is reset to default and then
1177 * only overwritten with a potential non-default value if explicitly specified in the config source. (Otherwise
1178 * the option is accumulating, hence whatever value is already stored in the `Value_set` is left unchanged unless
1179 * specified in the config source.)
1180 *
1181 * @note This may be useful, in particular, when used in flow::cfg::Config_manager in multi-file updates for special
1182 * options that implement conditionality. It does not make sense to accumulate such options' values from file to
1183 * file in a given update, so this `_NO_ACC` variant macro makes sense to use.
1184 *
1185 * @param ARG_m_value
1186 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1187 * @param ARG_description
1188 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1189 * @param ARG_bool_validate_expr
1190 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1191 */
1192#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC(ARG_m_value, ARG_description, ARG_bool_validate_expr) \
1193 FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, true)
1194
1195/**
1196 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION(), but with support for setting a value at a container subscript.
1197 *
1198 * It works as follows. Normally `ARG_m_value`, when stringified via `#` preprocessor trick, looks like a sequence of
1199 * `m_...` of identifiers joined with `.` and/or `->`. Call these *terms*. In this case, however, exactly one of the
1200 * terms -- but not the trailing term -- must end with the postfix `[...]`, where `...` may be 1 or more characters
1201 * excluding `]`. As in FLOW_CFG_OPTION_SET_DECLARE_OPTION(), the option name will be auto-computed based on
1202 * the stringification; but with one extra step at the end: `[...]` shall be replaced by the added macro
1203 * arg `ARG_key`, which must be `ostream<<`able.
1204 *
1205 * This can be used to fill out fixed-key-set containers of `struct`s concisely. Otherwise one would have to
1206 * tediously unroll the loop and manually provide the name via FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED().
1207 * Perhaps best shown by example:
1208 *
1209 * ~~~
1210 * // In Value_set:
1211 * constexpr size_t S_N_GRAND_PARENTS = 4;
1212 * struct Grand_parent { string m_name; bool m_male_else_female = false; };
1213 * array<Grand_parent, S_N_GRAND_PARENTS> m_grand_parents;
1214 * // When declaring options for Option_set<Value_set>:
1215 * for (size_t idx = 0; idx != Value_set::S_N_GRAND_PARENTS; ++idx)
1216 * {
1217 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_name, "The grandparent's name.", true, idx);
1218 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_male_else_female, "Their gender.", true, idx);
1219 * }
1220 * // Resulting config options:
1221 * "grand-parents.0.name"
1222 * "grand-parents.0.male-else-female"
1223 * "grand-parents.1.name"
1224 * "grand-parents.1.male-else-female"
1225 * "grand-parents.2.name"
1226 * "grand-parents.2.male-else-female"
1227 * "grand-parents.3.name"
1228 * "grand-parents.3.male-else-female"
1229 * // Example file:
1230 * [grand-parents.0]
1231 * name=Alice Eve
1232 * male-else-female=false
1233 * [grand-parents.1]
1234 * name=Ray Liotta
1235 * male-else-female=true
1236 * ...
1237 * ~~~
1238 *
1239 * Tip: In this example `idx` is a number; and the container is an `array`. However, it can be any container with
1240 * both keys and values; it just needs to always have the same key set after `Value_set` construction: `map`, `vector`,
1241 * etc. Accordingly the key can be anything `ostream<<`-able (e.g., `string` works). However -- behavior is undefined
1242 * if the value, when `ostream<<`ed, would not be accepted by boost.program_options as an option name postfix.
1243 * Informally: keep it to alphanumerics and underscores.
1244 *
1245 * @note Do not confuse this with the situation where *one* option is *itself* a `vector<T>`, where `T` is a scalar
1246 * (from boost.program_options' point of view). E.g., a `vector<int>` can simply be a single option, and
1247 * the user can supply as many or as few elements as they want, at runtime (in config file, one option per line).
1248 * For example the above `Value_set` can *itself* contain a `vector<>` in *each* `struct` in the array:
1249 *
1250 * ~~~
1251 * // In Value_set:
1252 * struct Grand_parent { ...; vector<int> m_kid_ages; };
1253 * // When declaring options for Option_set<Value_set>:
1254 * for (size_t idx = 0; idx != Value_set::S_N_GRAND_PARENTS; ++idx)
1255 * {
1256 * ...
1257 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(m_grand_parents[idx].m_kid_ages, "Children's age list.", true, idx);
1258 * }
1259 * // Resulting config options:
1260 * ...
1261 * "grand-parents.0.kid-ages"
1262 * ...
1263 * "grand-parents.1.kid-ages"
1264 * ...
1265 * "grand-parents.2.kid-ages"
1266 * ...
1267 * "grand-parents.3.kid-ages"
1268 * // Example file:
1269 * [grand-parents.0]
1270 * name=Alice Eve
1271 * male-else-female=false
1272 * kid-ages=5
1273 * kid-ages=7
1274 * kid-ages=25
1275 * [grand-parents.1]
1276 * name=Ray Liotta
1277 * male-else-female=true
1278 * kid-ages=32
1279 * ...
1280 * ~~~
1281 *
1282 * @param ARG_m_value
1283 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1284 * @param ARG_description
1285 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1286 * @param ARG_bool_validate_expr
1287 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1288 * @param ARG_key
1289 * See above.
1290 */
1291#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED(ARG_m_value, ARG_description, ARG_bool_validate_expr, ARG_key) \
1292 FLOW_UTIL_SEMICOLON_SAFE \
1293 ( \
1294 const ::std::string FLOW_CFG_SET_DECL_OPT_KEYED_name \
1295 = ::flow::cfg::value_set_member_id_to_opt_name_keyed(#ARG_m_value, ARG_key); \
1296 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1297 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_KEYED_name.c_str(), ARG_description, ARG_bool_validate_expr, false); \
1298 )
1299
1300/**
1301 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED_NO_ACC(), except the option is marked as *non-accumulating*
1302 * in the same sense as for FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC().
1303 *
1304 * @see FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() for a brief explanation of how non-accumulating options work.
1305 *
1306 * @param ARG_m_value
1307 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1308 * @param ARG_description
1309 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1310 * @param ARG_bool_validate_expr
1311 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1312 * @param ARG_key
1313 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED().
1314 */
1315#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED_NO_ACC(ARG_m_value, ARG_description, ARG_bool_validate_expr, ARG_key) \
1316 FLOW_UTIL_SEMICOLON_SAFE \
1317 ( \
1318 const ::std::string FLOW_CFG_SET_DECL_OPT_KEYED_name \
1319 = ::flow::cfg::value_set_member_id_to_opt_name_keyed(#ARG_m_value, ARG_key); \
1320 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1321 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_KEYED_name.c_str(), ARG_description, ARG_bool_validate_expr, true); \
1322 )
1323
1324/**
1325 * As of this writing is identical to either FLOW_CFG_OPTION_SET_DECLARE_OPTION() or
1326 * FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC(), depending on the value of `ARG_no_accumulation` argument.
1327 *
1328 * Rationale: Currently we don't necessarily expect this to be widely used directly in place of the aforementioned two
1329 * macros, but it seems reasonable to have a single macro with all various knobs given as parameters with certain
1330 * knob combinations potentially invoked via macro(s) based on this one. As of this
1331 * writing there is just the one extra knob, `ARG_no_accumulation`, but in the future we might expand to more
1332 * variations in which case more args would be added here.
1333 *
1334 * @param ARG_m_value
1335 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1336 * @param ARG_description
1337 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1338 * @param ARG_bool_validate_expr
1339 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1340 * @param ARG_no_accumulation
1341 * `bool` such that `false` causes FLOW_CFG_OPTION_SET_DECLARE_OPTION() behavior, while `true`
1342 * causes FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() behavior.
1343 */
1344#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(ARG_m_value, ARG_description, ARG_bool_validate_expr, \
1345 ARG_no_accumulation) \
1346 FLOW_UTIL_SEMICOLON_SAFE \
1347 ( \
1348 const ::std::string FLOW_CFG_SET_DECL_OPT_name = ::flow::cfg::value_set_member_id_to_opt_name(#ARG_m_value); \
1349 FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED \
1350 (ARG_m_value, FLOW_CFG_SET_DECL_OPT_name.c_str(), ARG_description, ARG_bool_validate_expr, \
1351 ARG_no_accumulation); \
1352 )
1353
1354/**
1355 * Identical to FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS(), except the user must specify the option's string name
1356 * manually as an argument to the functional macro. (FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS() itself
1357 * is basically FLOW_CFG_OPTION_SET_DECLARE_OPTION() plus certain knobs which the former sets to commonly-used values
1358 * for concision.)
1359 *
1360 * This macro, which can be used directly but normally is not, is internally invoked (potentially indirectly) by any
1361 * other `FLOW_CFG_OPTION_SET_DECLARE_OPTION*()` macro. As such it is the essential core such macro.
1362 *
1363 * Informally: it is best to avoid its direct use, as it can break the auto-naming conventions maintained by
1364 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() and similar. That said a couple of use cases might be:
1365 * - FLOW_CFG_OPTION_SET_DECLARE_OPTION_KEYED() uses it in its impl to add a convention for container subscripts.
1366 * - One can specify a legacy option-name alias. (E.g., one can FLOW_CFG_OPTION_SET_DECLARE_OPTION()
1367 * an option and then FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED() a manually-named option targeting
1368 * the same `Value_set` member. Then either the auto-generated name or the legacy name can be used in a config
1369 * source, until the latter is deprecated-out successfully in the field.)
1370 *
1371 * @param ARG_opt_name_c_str
1372 * The option's manually specified string name. Type: directly convertible to `const char*`; typically
1373 * a string literal.
1374 * @param ARG_m_value
1375 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1376 * @param ARG_description
1377 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1378 * @param ARG_bool_validate_expr
1379 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION().
1380 * @param ARG_no_accumulation
1381 * See FLOW_CFG_OPTION_SET_DECLARE_OPTION_WITH_KNOBS().
1382 */
1383#define FLOW_CFG_OPTION_SET_DECLARE_OPTION_MANUALLY_NAMED(ARG_m_value, ARG_opt_name_c_str, \
1384 ARG_description, ARG_bool_validate_expr, \
1385 ARG_no_accumulation) \
1386 FLOW_UTIL_SEMICOLON_SAFE \
1387 ( \
1388 char const * const FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str = ARG_opt_name_c_str; \
1389 /* Subtlety: This is only safe to use here synchronously. */ \
1390 const ::flow::util::String_view FLOW_CFG_SET_DECL_OPT_MANUAL_name_view{FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str}; \
1391 const bool FLOW_CFG_SET_DECL_OPT_MANUAL_no_acc = ARG_no_accumulation; \
1392 switch (args.m_call_type) \
1393 { \
1394 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_PARSING_ROLE_OPT_TABLE: \
1395 { \
1396 using Value = decltype(args.m_args.m_fill_parsing_role_opt_table_args.m_values_candidate->ARG_m_value); \
1397 /* Subtlety: Copy input NUL-terminated char* into lambda string capture just in case they give us */ \
1398 /* not the usual `static` string literal as ARG_opt_name_c_str. */ \
1399 flow::Function<bool (const Value&)> FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func \
1400 = [FLOW_CFG_SET_DECL_OPT_MANUAL_name_std_str = std::string{FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str}] \
1401 (const Value&) -> bool { return ARG_bool_validate_expr; }; \
1402 args.m_args.m_fill_parsing_role_opt_table_args.m_option_set \
1403 ->declare_option_for_parsing \
1404 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1405 &args.m_args.m_fill_parsing_role_opt_table_args.m_values_candidate->ARG_m_value, \
1406 /* Default is irrelevant if option accumulates from parse to parse. Pass null. */ \
1407 /* Default from Value_set{} shall be in effect at construction time, but that's it. */ \
1408 /* However if it's set as a non-accumulating option via knob, then pass-through the default: */ \
1409 /* each parse via boost.program_options shall first reset option to that default; then if present */ \
1410 /* overwrite that default. Hence the value from any preceding parse is always forgotten. */ \
1411 FLOW_CFG_SET_DECL_OPT_MANUAL_no_acc \
1412 ? &args.m_args.m_fill_parsing_role_opt_table_args.m_values_default_no_acc->ARG_m_value \
1413 : nullptr, \
1414 ::std::move(FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func), \
1415 ::flow::util::String_view{#ARG_bool_validate_expr}); \
1416 break; \
1417 } \
1418 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_OUTPUT_HELP_ROLE_OPT_TABLE: \
1419 args.m_args.m_fill_output_help_role_opt_table_args.m_option_set \
1420 ->declare_option_for_help \
1421 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1422 args.m_args.m_fill_output_help_role_opt_table_args.m_values_default->ARG_m_value, \
1423 ARG_description); \
1424 break; \
1425 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE: \
1426 ::flow::cfg::Option_set_base::declare_option_for_output \
1427 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1428 args.m_args.m_fill_output_current_role_opt_table_args.m_target_opts, \
1429 args.m_args.m_fill_output_current_role_opt_table_args.m_values_default->ARG_m_value, \
1430 args.m_args.m_fill_output_current_role_opt_table_args.m_values_current->ARG_m_value, \
1431 ARG_description); \
1432 break; \
1433 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_COMPARE_PARSED_VALS: \
1434 args.m_args.m_compare_parsed_vals_args.m_option_set \
1435 ->scan_parsed_option \
1436 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1437 args.m_args.m_compare_parsed_vals_args.m_option_set->values().ARG_m_value); \
1438 break; \
1439 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_LOAD_VALS_AS_IF_PARSED: \
1440 args.m_args.m_load_val_as_if_parsed_args.m_option_set \
1441 ->load_option_value_as_if_parsed \
1442 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1443 &args.m_args.m_load_val_as_if_parsed_args.m_values_candidate->ARG_m_value, \
1444 args.m_args.m_load_val_as_if_parsed_args.m_values_to_load->ARG_m_value); \
1445 break; \
1446 case ::flow::cfg::Option_set_base::Declare_options_func_call_type::S_VALIDATE_STORED_VALS: \
1447 { \
1448 /* Set up validator func similarly to above; see those comments. */ \
1449 using Value = decltype(args.m_args.m_validate_stored_vals_args.m_values_to_validate->ARG_m_value); \
1450 flow::Function<bool (const Value&)> FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func \
1451 = [FLOW_CFG_SET_DECL_OPT_MANUAL_name_std_str = std::string{FLOW_CFG_SET_DECL_OPT_MANUAL_name_c_str}] \
1452 (const Value&) -> bool { return ARG_bool_validate_expr; }; \
1453 /* Throw if invalid; else no-op. */ \
1454 ::flow::cfg::Option_set_base::validate_parsed_option \
1455 (FLOW_CFG_SET_DECL_OPT_MANUAL_name_view, \
1456 args.m_args.m_validate_stored_vals_args.m_values_to_validate->ARG_m_value, \
1457 ::std::move(FLOW_CFG_SET_DECL_OPT_MANUAL_validator_func), \
1458 ::flow::util::String_view{#ARG_bool_validate_expr}); \
1459 break; \
1460 } \
1461 /* No `default:` intentionally: most compilers should catch a missing enum value and warn. */ \
1462 } \
1463 ) // FLOW_UTIL_SEMICOLON_SAFE()
1464
1465// Template implementations.
1466
1467template<typename Value_set>
1469 Declare_options_func&& declare_opts_func_moved) :
1470 log::Log_context(logger_ptr, Flow_log_component::S_CFG),
1471 m_nickname(nickname),
1472 m_null(true),
1473 m_declare_opts_func(std::move(declare_opts_func_moved)),
1474 m_parsing(false)
1475{
1476 // Avoid INFO logging, in case they're only creating us to print some help and exit. Not essential info here.
1477 FLOW_LOG_TRACE("Option_set[" << *this << "]: Created with default values payload. Options table setup begins.");
1478
1479 // Refer to the doc headers for Opt_table and Declare_options_func to understand the next couple of calls.
1480
1481 // Load the *parsing-role* Opt_table m_opts_for_parsing. Its contents won't change after this.
1485 m_declare_opts_func(args);
1486 // m_null is now permanently true or false.
1487
1488 /* Load the *output-role* (help text sub-role) Opt_table. The defaults source is the current Value_set m_values
1489 * itself (since it was just loaded up with defaults by definition). It again won't change after this. */
1492 m_declare_opts_func(args);
1493
1494 FLOW_LOG_TRACE("Option_set[" << *this << "]: Options table setup finished. Null set? = [" << null() << "].");
1495} // Option_set::Option_set()
1496
1497template<typename Value_set>
1499{
1500 return m_null;
1501}
1502
1503template<typename Value_set>
1504const Value_set& Option_set<Value_set>::values() const
1505{
1506 return m_values;
1507}
1508
1509template<typename Value_set>
1511{
1512 return Mutable_values_ptr{new Values{values()}};
1513}
1514
1515template<typename Value_set>
1517{
1518 return m_parsing ? &m_values_candidate : nullptr;
1519}
1520
1521template<typename Value_set>
1522void Option_set<Value_set>::values_to_ostream(std::ostream& os, const Values* values_or_null) const
1523{
1524 /* Provide more real estate than the default 80, since this is chiefly for value output, probably in logs.
1525 * And just put the description on the next line always. */
1526 constexpr unsigned int LINE_LENGTH = 1000;
1527 constexpr unsigned int DESC_LENGTH = LINE_LENGTH - 1;
1528
1529 Opt_table opts_for_output{LINE_LENGTH, DESC_LENGTH};
1530
1532 args.m_call_type = Declare_options_func_args::Call_type::S_FILL_OUTPUT_CURRENT_ROLE_OPT_TABLE;
1534 = { &opts_for_output, &m_values_default,
1535 values_or_null ? values_or_null : &m_values };
1536 m_declare_opts_func(args);
1537
1538 os << opts_for_output; // Leverage boost.program_options to print the nicely formatted current+defaults+descriptions.
1539}
1540
1541template<typename Value_set>
1542void Option_set<Value_set>::log_values(util::String_view summary, const Value_set* values_or_null, log::Sev sev) const
1543{
1545 using std::flush;
1546
1547 String_ostream os;
1548 values_to_ostream(os.os(), values_or_null);
1549 os.os() << flush;
1550 FLOW_LOG_WITH_CHECKING(sev, "Option_set[" << *this << "]: Values payload [" << summary << "]:\n" << os.str());
1551}
1552
1553template<typename Value_set>
1554void Option_set<Value_set>::help_to_ostream(std::ostream& os) const
1555{
1556 os << m_opts_for_help; // Leverage boost.program_options to print the nicely formatted defaults+descriptions.
1557}
1558
1559template<typename Value_set>
1561{
1563 using std::flush;
1564
1565 String_ostream os;
1566 help_to_ostream(os.os());
1567 os.os() << flush;
1568 FLOW_LOG_WITH_CHECKING(sev, "Config usage [" << summary << "]:\n" << os.str());
1569}
1570
1571template<typename Value_set>
1572const boost::unordered_set<std::string>& Option_set<Value_set>::option_names() const
1573{
1574 return m_opt_names;
1575}
1576
1577template<typename Value_set>
1578template<typename Value>
1580 Value* target_value, const Value* value_default_if_no_acc,
1581 Function<bool (const Value& val)>&& validator_func_moved,
1582 util::String_view validator_cond_str_view)
1583{
1585 using opts::value;
1586 using std::string;
1587 using std::flush;
1588
1589 assert(target_value);
1590 string name(name_view);
1591
1592 m_opt_names.insert(name);
1593
1594 // Avoid INFO logging, in case they're only creating us to print some help and exit. Not essential info here.
1595 FLOW_LOG_TRACE("Option_set[" << *this << "]: Options table of parsing-role: "
1596 "Declaring parsing-capable option [" << name << "]; raw shallow size [" << sizeof(Value) << "].");
1597
1598 /* *Parsing-role* target is within m_values_candidate. No default: if not in config, it'll just be left untouched.
1599 * The validator function we create shall throw on validation failure, having been passed `const Value& val` by the
1600 * options engine. */
1601 const auto val_spec = value<Value>(target_value)
1602 ->notifier(throw_on_invalid_func(name_view, std::move(validator_func_moved),
1603 validator_cond_str_view));
1604 /* However: if non-accumulating mode is enabled then, in fact, set the value from Value_set{}
1605 * as default_value(); so that starting to parse a config source (e.g., config file) shall always reset to default
1606 * first instead of accumulating from a previous parse (if any). This matters for a given parse only if this
1607 * option is not specified in that config source. */
1608 if (value_default_if_no_acc)
1609 {
1610 // Subtlety re. default_value(2 args): Same as in declare_option_for_help().
1611 String_ostream default_str_os;
1612 value_to_ostream(default_str_os.os(), *value_default_if_no_acc);
1613 default_str_os.os() << flush;
1614
1615 val_spec->default_value(*value_default_if_no_acc, default_str_os.str());
1616 }
1617
1618 m_opts_for_parsing.add_options()(name.c_str(), val_spec);
1619
1620 m_null = false;
1621} // Option_set::declare_option_for_parsing()
1622
1623template<typename Value>
1625 (util::String_view name_view,
1626 Function<bool (const Value& val)>&& validator_func_moved,
1627 util::String_view validator_cond_str_view) // Static.
1628{
1631 using std::string;
1632 using std::flush;
1633
1634 return [validator_func = std::move(validator_func_moved),
1635 validator_cond_str = string{validator_cond_str_view},
1636 name = string{name_view}]
1637 (const Value& val)
1638 {
1639 if (!(validator_func(val)))
1640 {
1641 String_ostream msg_os;
1642 msg_os.os() << "Option [" << name << "]: "
1643 "Validation failed; the following condition must hold: [" << validator_cond_str << "]. "
1644 "Option value `val` = [";
1645 value_to_ostream(msg_os.os(), val);
1646 msg_os.os() << "]." << flush;
1647 throw Runtime_error{msg_os.str()};
1648 }
1649 };
1650} // Option_set_base::throw_on_invalid_func()
1651
1652template<typename Value_set>
1653template<typename Value>
1655 const Value& value_default, util::String_view description)
1656{
1658 using opts::value;
1659 using std::string;
1660 using std::flush;
1661
1662 assert(!description.empty());
1663
1664 /* Subtlety: The is also a ->default_value(1 arg) overload, which always uses ostream<< for output, but (as
1665 * elsewhere) run it through our possible output massaging machinery by providing the string to output. Hence use
1666 * the 2-arg version which takes an explicit string. @todo Could also implement a proxy type with an operator<<
1667 * ostream overload to make these call sites more elegant/concise. */
1668 String_ostream default_str_os;
1669 value_to_ostream(default_str_os.os(), value_default);
1670 default_str_os.os() << flush;
1671
1672 m_opts_for_help.add_options()
1673 (string{name}.c_str(),
1674 // *Output-role* has no target; it does output, not input.
1675 value<Value>()
1676 ->default_value(value_default, default_str_os.str()),
1677 string{description}.c_str());
1678
1679 // No need to log: logs are only interesting when setting up the parsing table. This'd just be verbose/redundant.
1680} // Option_set::declare_option_for_help()
1681
1682template<typename Value>
1684 const Value& value_default, const Value& current_value,
1685 util::String_view description) // Static.
1686{
1688 using opts::value;
1689 using std::string;
1690 using std::flush;
1691
1692 assert(!description.empty());
1693
1694 /* This is similar to declare_option_for_help() with 2 differences:
1695 * - The target is passed to us, *target_opts, as they will be printing that, and it depends on a current value
1696 * which changes over time, hence we can't just plant this in some data member the way we did with
1697 * m_opts_for_help.
1698 * - In addition to the default, add the current value into the output by massaging the description text.
1699 * We are printing a current value which will go into the description text here, while the default will
1700 * go into the default slot of the "usage" output to ostream. */
1701 String_ostream description_os;
1702
1703 // Visually match how boost.program_options prints the default value above us.
1704
1705 description_os.os() << " (";
1706 /* Always pass any user output of Value_set members through our possible specialization/overload around ostream<<,
1707 * as we may want to apply some us-specific massaging. */
1708 value_to_ostream(description_os.os(), current_value);
1709 description_os.os() << ") <==current | default==^\n"
1710 " " << description << flush;
1711
1712 // Subtlety re. default_value(2 args): Same as in declare_option_for_help().
1713 String_ostream default_str_os;
1714 value_to_ostream(default_str_os.os(), value_default);
1715 default_str_os.os() << flush;
1716
1717 target_opts->add_options()
1718 (string{name}.c_str(),
1719 // *Output-role* has no target; it does output, not input.
1720 value<Value>()
1721 ->default_value(value_default, default_str_os.str()),
1722 description_os.str().c_str());
1723
1724 // No need to log: logs are only interesting when setting up the parsing table. This'd just be verbose/redundant.
1725} // Option_set_base::declare_option_for_output()
1726
1727template<typename Value_set>
1728template<typename Value>
1729void Option_set<Value_set>::scan_parsed_option(util::String_view name_view, const Value& canonical_value)
1730{
1731 using boost::any_cast;
1732 using std::string;
1733 string name{name_view};
1734
1735 /* In this mode we are basically to check whether the value just parsed for `name`, which is in
1736 * m_iterable_values_candidate[name], is actually *different* from the current canonical value, which is
1737 * in canonical_value. If so we are to leave it there; if not, we are to remove it,
1738 * thus maintaining the invariant wherein m_iterable_values_candidate mirrors m_values_candidate, except for those
1739 * options whose values have not changed. */
1740
1741 const auto it = m_iterable_values_candidate.find(name);
1742 if (it == m_iterable_values_candidate.end())
1743 {
1744 return; // Option was not parsed.
1745 }
1746 // else
1747
1748 const auto& new_val = any_cast<const Value&>(it->second);
1749 const auto& old_val = canonical_value;
1750
1751 if (old_val == new_val)
1752 {
1753 m_iterable_values_candidate.erase(name);
1754 }
1755 return;
1756} // Option_set::scan_parsed_option()
1757
1758template<typename Value_set>
1759template<typename Value>
1761 Value* target_value,
1762 const Value& source_value)
1763{
1764 using std::string;
1765 string name{name_view};
1766
1767 assert(target_value);
1768 assert(m_parsing);
1769
1770 /* Getting here is hairy to understand, but our task is simple: Modify *this as-if
1771 * *target_value -- which is a member of m_values_candidate -- was parsed from some config source, yielding
1772 * in valid value source_value. We must update our state (given that m_parsing is true) accordingly. */
1773
1774 // This is the obvious part: set the thing inside m_values_candidate.
1775 *target_value = source_value;
1776
1777 // Now mirror it in m_iterable_values_candidate to maintain that invariant.
1778 m_iterable_values_candidate[name] = source_value;
1779
1780 /* That's it. Just picture it as if <name_view contents>=<encoding of source_value> was in a config file,
1781 * and parse_config_file() just did a store()/notify() of that line alone and hence updated
1782 * m_iterable_values_candidate accordingly. What we did above equals that; it's just much simpler to do
1783 * than actual parsing, since we have the value given to us directly in source_value. */
1784}
1785
1786template<typename Value>
1788 Function<bool (const Value& val)>&& validator_func_moved,
1789 util::String_view validator_cond_str_view) // Static.
1790{
1791 const auto validate_or_throw_func = throw_on_invalid_func(name_view, std::move(validator_func_moved),
1792 validator_cond_str_view);
1793 validate_or_throw_func(value);
1794} // Option_set_base::validate_parsed_option()
1795
1796template<typename Value_set>
1798 (const fs::path& cfg_path, bool allow_unregistered, bool* success_or_null,
1799 const boost::unordered_set<std::string>& allowed_unregistered_opts_or_empty)
1800{
1803 using util::key_exists;
1804 using opts::notify;
1805 using opts::store;
1806 using opts::variables_map;
1807 using boost::system::system_category;
1808 using fs::ifstream;
1809 using std::exception;
1810 using std::string;
1811
1812 // If they invoked us in non-throwing mode, call selves recursively in throwing mode and catch it.
1813 if (success_or_null)
1814 {
1815 try
1816 {
1817 parse_config_file(cfg_path, allow_unregistered, nullptr, allowed_unregistered_opts_or_empty);
1818 }
1819 catch (const exception& exc)
1820 {
1821 // We already logged on exception; be quiet.
1822 *success_or_null = false;
1823 return;
1824 }
1825 *success_or_null = true;
1826 return;
1827 }
1828 // else: Throw an exception on error. Perf not a real concern.
1829
1830 if (!m_parsing)
1831 {
1832 m_parsing = true;
1833 FLOW_LOG_INFO("Option_set[" << *this << "]: State CANONICAL: Request to parse file [" << cfg_path << "]; "
1834 "entering state PARSING. Initial candidate values payload initialized from current canonical "
1835 "values payload. Details follow (TRACE log level).");
1836
1837 assert(m_iterable_values_candidate.empty());
1838 }
1839 else
1840 {
1841 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Request to parse file [" << cfg_path << "]; "
1842 "continuing by parsing this next config source. "
1843 "The values payload going into it is as it was after the previous config source. "
1844 "Details follow (TRACE log level).");
1845 }
1846
1847 log_values("pre-parsing candidate config", &m_values_candidate, log::Sev::S_TRACE);
1848
1849 ifstream ifs{cfg_path};
1850 if (!ifs)
1851 {
1852 const Error_code sys_err_code{errno, system_category()};
1854
1855 throw Runtime_error{sys_err_code, ostream_op_string("Could not open file [", cfg_path, "].")};
1856 }
1857 // else
1858
1859 /* parse_() can throw on invalid option values and so on! In case it managed to touch m_values_candidate -- save it.
1860 * Our doc header(s) guarantee config payload is untouched unless parsing succeeds.
1861 * @todo This may not be necessary. boost.program_options is solid, but there's an uncommon (for Boost) looseness
1862 * to the documentation, including about error handling, so we don't find it trustworthy enough not to do this and
1863 * stay maintainable. Still consider revisiting this and not catching the exception and not worrying about
1864 * backing this up -- if that's really how it works. */
1865 const auto values_candidate_backup = m_values_candidate;
1866 opts::parsed_options parsed_options{&m_opts_for_parsing};
1867 try
1868 {
1869 parsed_options = opts::parse_config_file(ifs, m_opts_for_parsing, allow_unregistered);
1870 }
1871 catch (const exception& exc)
1872 {
1873 m_values_candidate = values_candidate_backup;
1874 // m_iterable_values_candidate is set below, so it hasn't been touched from this config source yet.
1875
1876 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1877 "Parsing file [" << cfg_path << "]: Exception occurred; parse failed. Values payload untouched. "
1878 "Message = [" << exc.what() << "].");
1879 throw;
1880 }
1881 // Got here? No problem parsing (but we haven't validated). First a detour:
1882
1883 /* If we were told to allow at least some (possibly all) unregistered options:
1884 * Log (at appropriate level) the unregistered options; log only the name part of the tokens for each (ignoring
1885 * any values); and fail if an un-approved unknown option shows up. */
1886 if (allow_unregistered)
1887 {
1888 for (const auto& option : parsed_options.options)
1889 {
1890 if (option.unregistered)
1891 {
1892 assert(!option.original_tokens.empty());
1893 const string opt_name = option.original_tokens.front();
1894
1895 if (allowed_unregistered_opts_or_empty.empty())
1896 {
1897 // Do not use INFO level; this can get verbose.
1898 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Ignoring unregistered option named "
1899 "[" << opt_name << "]. We were not given an approved-list. Perhaps this file contains "
1900 "other option sets, or it has to do with forward/backward-compatibility.");
1901 }
1902 else
1903 {
1904 if (!key_exists(allowed_unregistered_opts_or_empty, opt_name))
1905 {
1906 // Same deal as above.
1907 m_values_candidate = values_candidate_backup;
1908
1909 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: Unregistered option named "
1910 "[" << opt_name << "] is not approved; parse failed. Values payload untouched.");
1911 throw Runtime_error{ostream_op_string("Unregistered option named [", opt_name,
1912 "] is not approved; parse failed.")};
1913 }
1914 // else
1915
1916 // Do not use INFO level; this can get verbose.
1917 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Ignoring unregistered option named "
1918 "[" << opt_name << "]; it is in the approved-list. Perhaps this file contains "
1919 "other option sets.");
1920 } // if (!allowed_unregistered_opts_or_empty.empty())
1921 } // if (option.unregistered)
1922 } // for (option : parsed_options.options)
1923 // We don't care about them further.
1924 }
1925 // else { Apparently there were no unknown options encountered, as it didn't throw when parsing. }
1926
1927 // Store into a variables_map (a glorified map<string, boost::any>) and m_values_candidate.
1928
1929 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Parsed general format successfully. "
1930 "Will now parse each option's value.");
1931
1932 /* As explained in ###Rationale### in Iterable_values doc header, we use a fresh opts::variables_map as an
1933 * intermediary and load the results from that into our Iterable_values, overwriting any values already there
1934 * that were indeed set during this parse_*(). In other words, for every configured option N, it either does not
1935 * exist in m_iterable_values_candidate (in which case the value is still at its canonical m_values value), or it does
1936 * as m_iterable_values_candidate[N] (in which case it has been parsed at least once in a previous config source's
1937 * parse step *and* is different from m_values). Then after store() it will either be created or changed or remain
1938 * unchanged. In the latter case we should remove it to maintain the invariant, wherein only keys for
1939 * changed-from-canonical options exist. */
1940 variables_map fresh_iterable_values;
1941 try
1942 {
1943 store(parsed_options, fresh_iterable_values);
1944 }
1945 catch (const exception& exc)
1946 {
1947 // Same deal as above.
1948 m_values_candidate = values_candidate_backup;
1949
1950 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1951 "Parsed file [" << cfg_path << "]: Exception occurred; individual option parsing failed. "
1952 "Values payload untouched. Message = [" << exc.what() << "].");
1953 throw;
1954 }
1955
1956 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Parsed each individual option successfully. "
1957 "Will now further validate each option value logically and store on success.");
1958
1959 /* notify() will finish things by loading m_values_candidate members and validating them.
1960 * notify() can throw, particularly due to our own custom validating notifiers (see declare_option_for_parsing()). */
1961 try
1962 {
1963 notify(fresh_iterable_values);
1964 }
1965 catch (const exception& exc)
1966 {
1967 // Same deal as above.
1968 m_values_candidate = values_candidate_backup;
1969
1970 FLOW_LOG_WARNING("Option_set[" << *this << "]: State PARSING: "
1971 "Parsed file [" << cfg_path << "]: Exception occurred; individual logical validation failed. "
1972 "Values payload untouched. Message = [" << exc.what() << "].");
1973 throw;
1974 }
1975
1976 // As noted above transfer from the clean variables_map into our Iterable_values.
1977 for (const auto& pair : fresh_iterable_values)
1978 {
1979 const auto& name = pair.first;
1980 const auto& var_value = pair.second.value(); // boost.any.
1981
1982 assert((!key_exists(m_iterable_values_candidate, name) ||
1983 (m_iterable_values_candidate[name].type() == var_value.type())));
1984
1985 m_iterable_values_candidate[name] = var_value; // Copying boost.any copies the payload it holds.
1986 }
1987
1988 // This is the part that removes each option such that it was just parsed, but the new value makes it == canonical.
1990 args.m_call_type = Declare_options_func_args::Call_type::S_COMPARE_PARSED_VALS;
1992 m_declare_opts_func(args);
1993
1994 /* m_values_candidate and its iterable mirror m_iterable_values_candidate have been filled in!
1995 * By definition the former contains the pre-parsing values, plus whatever was in the config source overwriting
1996 * zero or more of those. m_iterable_values_candidate fully mirrors by including only those values different from
1997 * m_values_candidate. This fact may be used in canonicalize_candidate(). (For now, though, we don't care about
1998 * m_iterable_values_candidate outside of keeping it updated after each parse.) */
1999
2000 FLOW_LOG_TRACE("Option_set[" << *this << "]: State PARSING: Validate/store succeded. "
2001 "Further validation deferred until attempt to enter state CANONICAL. "
2002 "Updated values payload details follow.");
2003 log_values("post-parsing candidate config", &m_values_candidate, log::Sev::S_TRACE);
2004} // Option_set::parse_config_file()
2005
2006template<typename Value_set>
2008{
2009 if (!m_parsing)
2010 {
2011 m_parsing = true;
2012 FLOW_LOG_INFO("Option_set[" << *this << "]: State CANONICAL: Request to load values from source struct "
2013 "[" << &src_values << "]; entering state PARSING. "
2014 "Any preceding canonical state will be ignored. Details follow (TRACE log level).");
2015
2016 assert(m_iterable_values_candidate.empty());
2017 }
2018 else
2019 {
2020 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Request to load values from source struct "
2021 "[" << &src_values << "]; continuing by parsing this next config source. "
2022 "Any current candidate values payload will be overwritten entirely. "
2023 "Details follow (TRACE log level).");
2024 }
2025
2026 /* The best way to explain our task is... to me (ygoldfel), it's like parse_config_file() of an imaginary file
2027 * that would have contained every possible option, with values src_values. We need to do the stuff to end up
2028 * in the same state. (We already updated m_parsing if needed.) Of course it's a lot easier to just load the values
2029 * as opposed to really parsing them (which can fail validation and other difficulties). However we still need
2030 * to update the same m_* state that the parsing would have. Namely:
2031 * - m_values_candidate should equal src_values (as far as each individual option goes).
2032 * - m_iterable_values_candidate must be updated accordingly (see its doc header). */
2033
2034 /* This work-horse will indeed update m_values_candidate as needed and mirror each value in
2035 * m_iterable_values_candidate[name]. In parse_config_file() that is what occurs through the step where
2036 * m_iterable_values_candidate is loaded from the variable_map generated by store()/notify(). */
2038 args.m_call_type = Declare_options_func_args::Call_type::S_LOAD_VALS_AS_IF_PARSED;
2039 args.m_args.m_load_val_as_if_parsed_args = { this, &m_values_candidate, &src_values };
2040 m_declare_opts_func(args);
2041
2042 /* Now, m_iterable_values_candidate contains every (config-enabled) member of a Values struct.
2043 * Note, in particular, that this overwrites anything that may have been parsed from other config sources
2044 * until that point (as our log messages indicate) -- as all of src_values was copied over.
2045 *
2046 * All we need now is to ensure m_iterable_values_candidate stores *only* the values that were changed
2047 * compared to the canonical m_values. For that we just do the same thing parse_config_file() would at this stage. */
2048 args.m_call_type = Declare_options_func_args::Call_type::S_COMPARE_PARSED_VALS;
2050 m_declare_opts_func(args);
2051
2052 log_values("post-loading (perhaps baseline or rewound) candidate config", &m_values_candidate, log::Sev::S_TRACE);
2053} // Option_set::parse_direct_values()
2054
2055template<typename Value_set>
2057{
2058 using util::key_exists;
2059
2060 assert(m_parsing);
2061
2062 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Entering state CANONICAL. "
2063 "Values payload finalized. Details follow (TRACE log level).");
2064 log_values("entering-CANONICAL-state finalized config", &m_values_candidate, log::Sev::S_TRACE);
2065
2066 /* Before finalizing m_values from m_values_candidate let's examine changes.
2067 * m_iterable_values_candidate maintains the invariant of only include those options whose values have changed
2068 * vs. m_values. So this is trivial. */
2069
2070 for (const auto& pair : m_iterable_values_candidate)
2071 {
2072 const auto& name = pair.first;
2073 FLOW_LOG_INFO("Option_set[" << *this << "]: Detected change in option named [" << name << "].");
2074 }
2075
2076 if (change_detected)
2077 {
2078 *change_detected = (!m_iterable_values_candidate.empty());
2079 }
2080
2081 // All is right with the world.
2082
2083 m_parsing = false;
2084 m_values = m_values_candidate;
2085 m_iterable_values_candidate.clear();
2086} // Option_set::canonicalize_candidate()
2087
2088template<typename Value_set>
2090{
2091 if (m_parsing)
2092 {
2093 FLOW_LOG_INFO("Option_set[" << *this << "]: State PARSING: Rejecting candidate payload built; "
2094 "reverting to state CANONICAL.");
2095 m_parsing = false;
2096 m_iterable_values_candidate.clear();
2097 // m_values remains unchanged. m_values_candidate becomes meaningless (see its doc header: m_parsing == false now).
2098 }
2099}
2100
2101template<typename Value_set>
2102void Option_set<Value_set>::validate_values(bool* success_or_null) const
2103{
2104 validate_values(values(), success_or_null);
2105}
2106
2107template<typename Value_set>
2109{
2110 const auto candidate = values_candidate();
2111 assert(candidate && "Do not invoke validate_values_candidate() in CANONICAL state; only while PARSING.");
2112 validate_values(*candidate, success_or_null);
2113}
2114
2115template<typename Value_set>
2116void Option_set<Value_set>::validate_values(log::Logger* logger_ptr, const Values& values_to_validate,
2117 const Declare_options_func& declare_opts_func,
2118 bool* success_or_null) // Static.
2119{
2120 using std::exception;
2121
2122 // If they invoked us in non-throwing mode, call selves recursively in throwing mode and catch it.
2123 if (success_or_null)
2124 {
2125 try
2126 {
2127 validate_values(logger_ptr, values_to_validate, declare_opts_func);
2128 }
2129 catch (const exception& exc)
2130 {
2131 // We already logged on exception; be quiet.
2132 *success_or_null = false;
2133 return;
2134 }
2135 *success_or_null = true;
2136 return;
2137 }
2138 // else: Throw an exception on error. Perf not a real concern.
2139
2140 FLOW_LOG_SET_CONTEXT(logger_ptr, Flow_log_component::S_CFG);
2141
2143 args.m_call_type = Declare_options_func_args::Call_type::S_VALIDATE_STORED_VALS;
2144 args.m_args.m_validate_stored_vals_args.m_values_to_validate = &values_to_validate;
2145 try
2146 {
2147 /* Simple as that! This will invoke FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each option; and that will
2148 * simply call validate_parsed_option() with all the needed info like name, current value, validation condition
2149 * string, and most importantly the validator function created from the validation Boolean expression user
2150 * passed to FLOW_CFG_OPTION_SET_DECLARE_OPTION(). validate_parsed_option() will throw when invoked on an invalid
2151 * value. Catch it to log it, as promised, but then re-throw it, as if it fell through. */
2152 declare_opts_func(args);
2153 }
2154 catch (const exception& exc)
2155 {
2156 FLOW_LOG_WARNING("Stand-alone validation check failed: Details: [" << exc.what() << "].");
2157 throw;
2158 }
2159} // void Option_set::validate_values()
2160
2161template<typename Value_set>
2162void Option_set<Value_set>::validate_values(const Values& values_to_validate, bool* success_or_null) const
2163{
2164 validate_values(get_logger(), values_to_validate, m_declare_opts_func, success_or_null);
2165}
2166
2167template<typename Value_set>
2168std::ostream& operator<<(std::ostream& os, const Option_set<Value_set>& val)
2169{
2170 return os << '[' << val.m_nickname << "]@" << &val;
2171}
2172
2173template<typename Value>
2174void value_to_ostream(std::ostream& os, const Value& val)
2175{
2176 os << val;
2177}
2178
2179template<typename Rep, typename Period>
2180void value_to_ostream(std::ostream& os, const boost::chrono::duration<Rep, Period>& val)
2181{
2183 using util::String_view;
2184 using boost::chrono::ceil;
2185 using boost::chrono::nanoseconds;
2186 using boost::chrono::microseconds;
2187 using boost::chrono::milliseconds;
2188 using boost::chrono::seconds;
2189 using boost::chrono::minutes;
2190 using boost::chrono::hours;
2191 using std::string;
2192
2193 using raw_ticks_t = uint64_t; // Should be big enough for anything.
2194
2195 // Nanoseconds should be the highest precision we use anywhere. It won't compile if this can lose precision.
2196 nanoseconds val_ns{val};
2197
2198 // Deal with positives only... why not?
2199 raw_ticks_t raw_abs_ticks = (val_ns.count() < 0) ? (-val_ns.count()) : val_ns.count();
2200
2201 /* We can of course simply `os << val_ns` -- and it'll print something like "<...> ns" which is correct.
2202 * Instead we'd like to use more convenient units -- but without losing precision due to conversion; so convert
2203 * up to the most coarse units possible. First, though, take care of the special case 0: */
2204 if (raw_abs_ticks == 0)
2205 {
2206 os << seconds::zero(); // Seconds are a nice default. It's strange to say "0 hours" or what-not.
2207 return;
2208 }
2209 // else
2210
2211 // Then keep trying this until can no longer try a coarser unit without losing precision.
2212 const auto try_next_unit = [&](auto pre_division_dur, raw_ticks_t divide_by) -> bool
2213 {
2214 if ((raw_abs_ticks % divide_by) == 0)
2215 {
2216 // Dividing current raw_abs_ticks by divide_by does not lose precision; so at worst can output post-division dur.
2217 raw_abs_ticks /= divide_by;
2218 return false;
2219 }
2220 // else: It does lose precision, so we must stop here and print it in pre-division units.
2221 os << ostream_op_string(pre_division_dur);
2222 return true;
2223 };
2224
2225 if (!(try_next_unit(val_ns, 1000)
2226 || try_next_unit(ceil<microseconds>(val), 1000)
2227 || try_next_unit(ceil<milliseconds>(val), 1000)
2228 || try_next_unit(ceil<seconds>(val), 60)
2229 || try_next_unit(ceil<minutes>(val), 60)))
2230 {
2231 os << ceil<hours>(val);
2232 }
2233} // value_to_ostream()
2234
2235template<typename Element>
2236void value_to_ostream(std::ostream& os, const std::vector<Element>& val)
2237{
2238 for (size_t idx = 0; idx != val.size(); ++idx)
2239 {
2240 os << '[';
2241 value_to_ostream<Element>(os, val[idx]);
2242 os << ']';
2243 if (idx != (val.size() - 1))
2244 {
2245 os << ' ';
2246 }
2247 }
2248} // value_to_ostream()
2249
2250template<typename Key>
2251std::string value_set_member_id_to_opt_name_keyed(util::String_view member_id, const Key& key)
2252{
2253 using util::String_view;
2255 using boost::regex;
2256 using boost::regex_match;
2257 using boost::smatch;
2258 using std::string;
2259
2260 // Keep in harmony with value_set_member_id_to_opt_name() of course.
2261
2262 /* <anything, long as possible>[<replaced stuff>]<.blah... or ->blah... though we don't enforce it here>
2263 * =>
2264 * <anything, long as possible>.<`key`><.blah... or ->blah... though we don't enforce it here> */
2265
2266 smatch matched_groups;
2267 /* @todo Should I be imbuing the regex with std::locale::classic() or something?
2268 * @todo Can probably make it work directly on String_view member_id; seems to conflict with `smatch`; but
2269 * probably it can be figured out. Perf at this level is hardly of high import so not worrying. */
2270 const string member_id_str{member_id};
2271#ifndef NDEBUG
2272 const bool matched_ok =
2273#endif
2274 regex_match(member_id_str, matched_groups, VALUE_SET_MEMBER_ID_TO_OPT_NAME_KEYED_REGEX);
2275 assert(matched_ok && "Member identifier must look like: <longest possible stuff>[<key to replace>]<the rest>");
2276
2277 constexpr char INDEX_SEP_BEFORE = '.';
2278
2279 /* Don't post-process or pre-process a single value_set_member_id_to_opt_name() result.
2280 * The replacer in that function shouldn't run across the substituted-in `key` (which could contain, e.g., "m_..." --
2281 * which would get incorrectly replaced by just "..."); and running the replacer followed by making the
2282 * [...]-by-dot-key substution is at least aesthetically perf-wasteful (those characters get replaced anyway later).
2283 * Anyway, all that aside, the following is easily defensible as clearly not having any such issues. */
2284 return ostream_op_string
2285 (value_set_member_id_to_opt_name(String_view{&*matched_groups[1].first,
2286 size_t(matched_groups[1].length())}),
2287 INDEX_SEP_BEFORE,
2288 key, // ostream<< it.
2289 value_set_member_id_to_opt_name(String_view{&*matched_groups[2].first,
2290 size_t(matched_groups[2].length())}));
2291} // value_set_member_id_to_opt_name_keyed()
2292
2293} // 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
void validate_values(bool *success_or_null=nullptr) const
Validates the current contents of values() using the validators *this Option_set<Value_set> is config...
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_...
void validate_values_candidate(bool *success_or_null=nullptr) const
Equivalent to validate_values(success_or_null) but validates *values_candidate() instead of 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 values_to_ostream(std::ostream &os, const Values *values_or_null=nullptr) const
Writes a multi-line user-suitable representation of the current values in a Values object,...
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 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...
void parse_config_file(const fs::path &cfg_path, bool allow_unregistered, bool *success_or_null=nullptr, 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 ...
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.
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 canonicalize_candidate(bool *change_detected=nullptr)
In PARSING state enters CANONICAL state, finalizing values() from values_candidate().
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.
Values m_values_default
Copy of values() when it is first constructed; i.e., the defaults.
void log_values(util::String_view summary, const Values *values_or_null=nullptr, log::Sev sev=log::Sev::S_INFO) const
Logs the given values payload using values_to_ostream().
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:1786
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1392
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:200
#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:155
#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:574
#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:473
#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:230
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, char sep_replacement='-')
Utility, used by FLOW_CFG_OPTION_SET_DECLARE_OPTION() internally but made available as a public API i...
Definition: option_set.cpp:41
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:248
@ 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.
void store(T *target_val, const boost::type_identity_t< T > &val)
Default store(&target_stats.m_val, val) that simply assigns *target_val = val.
Definition: stat_set.hpp:344
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:511
std::string ostream_op_string(T &&... ostream_args)
Equivalent to ostream_op_to_string() but returns a new string by value instead of writing to the call...
Definition: util.hpp:575
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:497
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:627
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:488
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:527
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:562
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:508
const Values * m_values_to_load
Values shall be copied from this external Value_set.
Definition: option_set.hpp:555
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:523
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:493
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:499
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.