Aim of this exercise is to ingest configuration files in (Windows-like) .ini-style (or Java-style .properties) format. In past, I have written code to ingest either (interchangably) using reflection to map into native Java (or Javascript, or Python) data structures. Using C/C++ does not allow the same.
Flip this around.
Contents of configuration files is only meaningful if ingested by source code. So source code defines what should be in a configuration file. Configuration read into POD (simple direct C++ data structure) is simplest to access.
Put differently, the aim here:
- Manually create an example INI (or properties) file with default values.
- Generate C++ code to read that INI/properties file into a POD.
- Access to settings is then simple POD (plain-old-data) structure fields.
Given an example INI file:
[foo]
bar=1
zot=abc
[panic]
when=sometimes
Configuration data becomes a simple class:
#pragma once
// Generated date: Sun Apr 27 21:52:36 2025
// Do not edit this file.
extern struct configuration_pod_t {
~configuration_pod_t();
struct context_o;
struct context_o* p_context;
bool pod_load(const char*);
bool pod_save(const char*);
struct configuration_pod_foo_t {
const char* v_bar;
const char* v_zot;
} s_foo;
struct configuration_pod_panic_t {
const char* v_when;
} s_panic;
} configuration_pod;Access to settings becomes:
// Once
configuration_pod.pod_load("settings.ini");
// Later
::printf("When should we panic: %s\n", configuration_pod.s_panic.v_when);INI files are grouped by [section] with associated key=value assignments.
PROPERTIES files are both more and less structured, with just us.bannister.key=value assignments.
Keys are structured like (reversed) Internet host names (a Java convention).
In languages that support reflection, those dotted-names can be mapped into native data structures.
You can unify both formats by treating INI files as yielding section.key=value.
Conversely, PROPERTIES can become [us.bannister] and key=value.
So a single reader can accept both formats.
(Yeh. I just liked the name.)
Ingests structured configuration files (either/both ini or properties).
Ingests the entire configuration file in a single read(),
then optimally splits key/values out of that block.
End is to generate C++ structures, then code to read into those structures.
Note there is no conversion from strings to other types. This is intentional.
Configuration files are just named bundles of strings. Validation is needed to ensure a string represents an acceptable value. The temptation is to bundle validation with configuration. This is a mistake. Validation is an independent concern.
The provided "pod/pod_valid.h" include with pod_valid::validator_o class is meant as an example. This is meant as an example pattern - which you extend to suit your application. (Note you might follow a similar pattern to validate incoming parameters on a web API.)
Usage is simple and clean.
auto v = pod_valid::validator_o(configuration_pod.s_panic.v_when).as_integer().in_range(1, 10).limit_range(2, 6);
::printf("in: %s value: %li valid: %u\n", configuration_pod.s_panic.v_when, v.value, v.valid);You can and should extend the above pattern to suit your use.
As an aside, my approach to naming might be unfamiliar.
With the usual CamelCase naming (and yes, I date before that was new), in a smart IDE, the grouping of members is a bit random. This bothered me.
Over time, I noticed that classes tended to have facets. When those facets were separable, they might turn into interfaces. But rather often the facets of an object were more dependent, and not truly separable from the class. (Should mention "mixins" from the Lisp world, but pre-web so links are scarce.)
You might also note that I use a minimalist version of "Hungarian" notation. (And yes, I spent time in Win16, where this got a bit extreme, of need.)
The minimalist end is:
- Method names with the most significant bit first. Something like:
facet_subfacet_operation() - Class names end with "_o".
- Object names start with "o_" (sometimes).
- Pointer names start with "p_".
- Index names start with "i_".
- Count names start with "n_".
- String names start with "s_" (mostly).
- Array names start with "a_" (sometimes).
Not remotely religious about any of the above, but works for me.