From b1a041fa25912380034991979ac7b8e9ec283090 Mon Sep 17 00:00:00 2001 From: kamchatka-volcano Date: Fri, 19 Jan 2024 20:37:59 +0300 Subject: [PATCH] -updated readme.md --- README.md | 513 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 374 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index a196c56..0703844 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,53 @@ [![build & test (clang, gcc, MSVC)](https://github.com/kamchatka-volcano/figcone/actions/workflows/build_and_test.yml/badge.svg?branch=master)](https://github.com/kamchatka-volcano/figcone/actions/workflows/build_and_test.yml) -`figcone` - is a C++17 library, providing a convenient declarative interface for configuration parsers and built-in support for reading `JSON`, `YAML`, `TOML`, `XML`, `INI` and `shoal` config files. To use it, create a configuration schema by declaring a structure for each level of your config file and load it by calling a method, matching the preferred configuration format: +`figcone` - is a C++17 / C++20 library, providing a convenient declarative interface for configuration parsers and +built-in support for reading `JSON`, `YAML`, `TOML`, `XML`, `INI` and `shoal` config files. To use it, create a +configuration schema by declaring a structure for each level of your config file and load it by calling a method, +matching the preferred configuration format: + +```C++ +///examples_static_refl/ex01_static_refl.cpp +/// +#include +#include +#include +#include + +struct ThumbnailCfg { + int maxWidth; + int maxHeight; +}; + +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnailSettings; +}; + +int main() +{ + auto cfgReader = figcone::ConfigReader{}; + auto cfg = cfgReader.readToml(R"( + rootDir = "~/Photos" + supportedFiles = [".jpg", ".png"] + [thumbnailSettings] + maxWidth = 256 + maxHeight = 256 + )"); + //At this point your config is ready to use + std::cout << "Launching PhotoViewer in directory " << cfg.rootDir << std::endl; +} +``` + +This example uses the static reflection interface based on the `pfr` library. It requires C++20 and only works with +aggregate initializable structures. On C++17, the runtime reflection solution originally developed with `figcone` is +used to register configuration fields: ```C++ ///examples/ex01.cpp /// -#include +#include #include #include #include @@ -19,8 +60,9 @@ struct ThumbnailCfg : public figcone::Config int maxWidth = param<&ThumbnailCfg::maxWidth>(); int maxHeight = param<&ThumbnailCfg::maxHeight>(); }; + struct PhotoViewerCfg : public figcone::Config{ - //config fields can also be created with macros: + //alternatively config fields can be created with macros: FIGCONE_PARAM(rootDir, std::filesystem::path); FIGCONE_PARAMLIST(supportedFiles, std::vector); FIGCONE_NODE(thumbnailSettings, ThumbnailCfg); @@ -38,27 +80,31 @@ int main() )"); //At this point your config is ready to use std::cout << "Launching PhotoViewer in directory " << cfg.rootDir << std::endl; - return 0; } ``` + ## Table of Contents + * [Usage](#usage) - * [Config structure](#Config-structure) - * [Supporting non-aggregate config structures](#supporting-non-aggregate-config-structures) - * [Registration without macros](#registration-without-macros) - * [Supported formats](#supported-formats) - * [JSON](#json) - * [YAML](#yaml) - * [TOML](#toml) - * [XML](#xml) - * [INI](#ini) - * [shoal](#shoal) - * [Creation of figcone-compatible parsers](#creation-of-figcone-compatible-parsers) - * [User defined types](#user-defined-types) - * [Unregistered fields handling](#unregister-fields-handling) - * [Validators](#validators) - * [Post-processors](#post-processors) + * [Config structure for runtime reflection (C++17)](#config-structure-for-runtime-reflection--c17-) + * [Supporting non-aggregate config structures](#supporting-non-aggregate-config-structures) + * [Registration without macros](#registration-without-macros) + * [Config structure for static reflection (C++20)](#config-structure-for-static-reflection--c20-) + * [Supported formats](#supported-formats) + * [JSON](#json) + * [YAML](#yaml) + * [TOML](#toml) + * [XML](#xml) + * [INI](#ini) + * [shoal](#shoal) + * [Creation of figcone-compatible parsers](#creation-of-figcone-compatible-parsers) + * [User defined types](#user-defined-types) + * [Unregistered fields handling](#unregister-fields-handling) + * [Validators](#validators) + * [Runtime reflection validators](#runtime-reflection-validators) + * [Static reflection validators](#static-reflection-validators) + * [Post-processors](#post-processors) * [Installation](#installation) * [Running tests](#running-tests) * [Building examples](#building-examples) @@ -66,11 +112,9 @@ int main() ## Usage +### Config structure for runtime reflection (C++17) -### Config structure - -To use `figcone`, you need to create a structure with fields corresponding to the config's parameters. -To do this, subclass `figcone::Config` and declare fields using the following macros: +To register configuration structure, subclass `figcone::Config` and declare fields using the following macros: - **FIGCONE_PARAM(`name`, `type`)** - creates a `type name;` config field and registers it in the parser. - **FIGCONE_PARAMLIST(`name`, `listType`)** - creates a `listType name;` config field and registers it in the parser. listType can be any sequence container that supports the `emplace_back` operation, such as `vector`, `deque`, or `list` from the STL. @@ -79,59 +123,29 @@ To do this, subclass `figcone::Config` and declare fields using the following ma - **FIGCONE_COPY_NODELIST(`name`, `listType`)** - creates a `listType name;` config field for a list of nested configuration structures and registers it in the parser. `listType` can be any sequence container that supports the `emplace_back` operation, such as `vector`, `deque`, or `list` from the STL. The type stored in the list (listType::value_type) must be a subclass of `figcone::Config`. The first element of this list acts as a template for the other elements, which means that all unspecified parameters of the second and following elements will be copied from the first element without raising a parsing error for missing parameters. - **FIGCONE_DICT(`name`, `mapType`)** - creates a `mapType name;` config field for a nested dictionary and registers it in the parser. `mapType` can be any associative container that supports the emplace operation, such as `map` or `unordered_map` from the STL. The key type of the map must be `std::string` The preprocessor doesn't handle commas between template arguments in the correct way, so you need to create an alias for your map in order to use it with this macro: + ```c++ using StringMap = std::map; FIGCONE_DICT(testDict, StringMap); ``` Notes: -- All config entities listed above provide the parenthesis operator `()` which sets the default value and makes this config field optional. This means that the field can be omitted from the configuration file without raising an error. The empty `operator ()` makes a field's value default initialized, otherwise the passed parameters are used for initialization. `FIGCONE_NODE`, `FIGCONE_NODELIST`, and `FIGCONE_COPY_NODELIST` only support default initialization. -- It is also possible to make any config field optional by placing it in `figcone::optional` (a `std::optional`-like wrapper with a similar interface). If a value for this field is missing from the config file, the field remains uninitialized and no error occurs. -- Types used for config parameters must be default constructible and copyable. - -You do not need to change your code style when declaring config fields. `camelCase`, `snake_case`, and `PascalCase` names are supported, and can be converted to the format used by parameter names in the config file. To do this, specify the configuration names format with the `figcone::NameFormat` enum by passing its value to the `figcone::ConfigReader` template argument. -To demonstrate it, let's change our PhotoViewer example to use snake_case names in the configuration: - -```C++ -///examples/ex02.cpp -/// -#include -#include -#include -#include +- All config entities listed above provide the parenthesis operator `()` which sets the default value and makes this + config field optional. This means that the field can be omitted from the configuration file without raising an error. + The empty `operator ()` makes a field's value default initialized, otherwise the passed parameters are used for + initialization. `FIGCONE_NODE`, `FIGCONE_NODELIST`, and `FIGCONE_COPY_NODELIST` only support default initialization. +- It is also possible to make any config field optional by placing it in `figcone::optional` (a `std::optional`-like + wrapper with a similar interface). If a value for this field is missing from the config file, the field remains + uninitialized and no error occurs. +- Types used for config parameters must be default constructible and copyable. -struct ThumbnailCfg : public figcone::Config -{ - int maxWidth = param<&ThumbnailCfg::maxWidth>(); - int maxHeight = param<&ThumbnailCfg::maxHeight>(); -}; -struct PhotoViewerCfg : public figcone::Config -{ - //config fields can also be created with macros: - FIGCONE_PARAM(rootDir, std::filesystem::path); - FIGCONE_PARAMLIST(supportedFiles, std::vector); - FIGCONE_NODE(thumbnailSettings, ThumbnailCfg); -}; +#### Supporting non-aggregate config structures -int main() -{ - auto cfgReader = figcone::ConfigReader{}; - auto cfg = cfgReader.readToml(R"( - root_dir = "/home/kamchatka-volcano/photos" - supported_files = [".jpg", ".png"] - [thumbnail_settings] - max_width = 256 - max_height = 256 - )"); - //At this point your config is ready to use - std::cout << "Launching PhotoViewer in directory " << cfg.rootDir << std::endl; - return 0; -} -``` +Runtime reflection interface of `figcone` relies on aggregate initialization of user-provided structures. If your config +object needs to contain private data or virtual functions, it becomes a non-aggregate type. In this case, you must use +the following `using` declaration to inherit `figcone::Config`'s constructors: `using Config::Config;` -### Supporting non-aggregate config structures -`figcone` relies on aggregate initialization of user-provided structures. If your config object needs to contain private data or virtual functions, it becomes a non-aggregate type. In this case, you must use the following `using` declaration to inherit `figcone::Config`'s constructors: `using Config::Config;` ```cpp struct PhotoViewerCfg : public figcone::Config { @@ -143,24 +157,28 @@ struct PhotoViewerCfg : public figcone::Config }; ``` -### Registration without macros -`figcone` can be used without macros, as every configuration entity described earlier can be registered with the similarly named `figcone::Config`'s member templates: +#### Registration without macros + +Runtime reflection interface of `figcone` can be used without macros, as every configuration entity described earlier +can be registered with the similarly named `figcone::Config`'s member templates: + ```c++ struct Cfg : public figcone::Config{ - int testParam = param<&Cfg::testParam>(); - int testParam2 = param<&Cfg::testParam2>()(100); - figcone::optional testParam3 = param<&Cfg::testParam3>(); - std::vector testParamList = paramList<&Cfg::testParamList>(); - TestNode testNode = node<&Cfg::testNode>(); - figcone::optional testNode2 = node<&Cfg::testNode2>(); - std::vector testNodeList = nodeList<&Cfg::testNodeList>(); - std::vector copyTestNodeList = copyNodeList<&Cfg::copyTestNodeList>(); + int testParam = param<&Cfg::testParam>(); + int testParam2 = param<&Cfg::testParam2>()(100); + figcone::optional testParam3 = param<&Cfg::testParam3>(); + std::vector testParamList = paramList<&Cfg::testParamList>(); + TestNode testNode = node<&Cfg::testNode>(); + figcone::optional testNode2 = node<&Cfg::testNode2>(); + std::vector testNodeList = nodeList<&Cfg::testNodeList>(); + std::vector copyTestNodeList = copyNodeList<&Cfg::copyTestNodeList>(); std::map testDict = dict<&Cfg::testDict>(); }; ``` -Internally, these methods use the [`nameof`](https://github.com/Neargye/nameof) library to get config fields' names as strings. By default, `figcone` ships without it, so these methods aren't available. To use them, enable the `FIGCONE_USE_NAMEOF` CMake option to automatically download and configure the nameof library, or install it on your system manually. -Note that nameof relies on non-standard functionality of C++ compilers, so if you don't like it, you can use `figcone` without it by providing names for config fields yourself: +Internally, these methods use the [`nameof`](https://github.com/Neargye/nameof) library to get config fields' names as +strings. Note that nameof relies on non-standard functionality of C++ compilers, so if you don't like it, you can +use `figcone` without it by providing names for config fields yourself: ```c++ struct Cfg : public figcone::Config{ @@ -175,50 +193,182 @@ Note that nameof relies on non-standard functionality of C++ compilers, so if yo std::map testDict = dict<&Cfg::testDict>("testDict"); }; ``` -Please note that on the MSVC compiler, the `nameof` features used by `figcone` require the C++20 standard. This is handled automatically by CMake configuration if MSVC is your default compiler. Otherwise, you will need to enable the C++20 standard manually. -Config structures declared using the macro-free methods are fully compatible with all of `figcone`'s functionality. Examples in the documentation use registration with macros, as it is the least verbose method. +Please note that on the MSVC compiler, the `nameof` features used by `figcone` require the C++20 standard. This is +handled automatically by CMake configuration if MSVC is your default compiler. Otherwise, you will need to enable the +C++20 standard manually. + +Config structures declared using the macro-free methods are fully compatible with all of `figcone`'s functionality. +Examples in the documentation use registration with macros, as it is the least verbose method. + +### Config structure for static reflection (C++20) + +Static reflection provided by the `pfr` library allows us to register configuration by using aggregate structures +without any base class and macros. Let's return to the example from the beginning of this document: + +```c++ +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnailSettings; +}; +``` + +All field types and names are registered during compile time. In this example, `rootDir` is registered as a +parameter, `supportedFiles` as a parameter list, and `thumbnailSettings` as a node. +If a field can be converted to a string (by a conversion operator, std::stringstream, or figcone::StringConverter—which +will be discussed later), it is registered as a parameter; otherwise, it is registered as a node. The same rule applies +to the container elements. +To create node or parameter lists, use any sequence container that supports the `emplace_back` operation, such +as `vector`, `deque`, or `list` from the STL. +To create dictionaries, use any associative container that supports the emplace operation, such as `map` +or `unordered_map` from the STL. The key type of the map must be `std::string`. + +Optional fields are created with `std::optional`; there's no need to use `figcone::optional`. If a field has a default +value, it's not ergonomic to place it in `std::optional` just to specify that it can be omitted from the config file. An +alternative way to make a field optional is using the `figcone::OptionalField` trait and placing it in +the `figcone::FieldTraits` type list. Let's make `supportedFiles` from the previous example optional to see how it's +done: + +```c++ +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnailSettings; + + using traits=figcone::FieldTraits< + figcone::OptionalField<&PhotoViewerCfg::supportedFiles> + >; +}; +``` + +Another trait is `figcone::CopyNodeListField`. It enables the use of a copy node list functionality. The first element +of such a list acts as a template for the other elements. This means that all unspecified parameters of the second and +following elements will be copied from the first element without raising a parsing error for missing parameters (it's +the same field type as `FIGCONE_COPYNODELIST` from the runtime reflection interface). + +Let's make `thumbnailSettings` a copy node list: + +```c++ +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + std::vector thumbnailSettings; + + using traits=figcone::FieldTraits< + figcone::OptionalField<&PhotoViewerCfg::supportedFiles>, + figcone::CopyNodeListField<&PhotoViewerCfg::thumbnailSettings> + >; +}; +``` + +Sadly, using such trait lists is currently the only possible way to add additional information about config fields. If +you have complex configuration that needs to have many traits, it might make sense to prefer the runtime reflection +interface to keep the code more concise. + +### Name format + +You do not need to change your code style when declaring config fields. `camelCase`, `snake_case`, and `PascalCase` +names are supported, and can be converted to the format used by parameter names in the config file. To do this, specify +the configuration names format with the `figcone::NameFormat` enum by passing its value to the `figcone::ConfigReader` +template argument. + +To demonstrate it, let's change our PhotoViewer example to use snake_case names in the configuration: + +```C++ +///examples_static_refl/ex02_static_refl.cpp +/// +#include +#include +#include +#include + +struct ThumbnailCfg { + int maxWidth; + int maxHeight; +}; + +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnailSettings; +}; + + +int main() +{ + auto cfgReader = figcone::ConfigReader{}; + auto cfg = cfgReader.readToml(R"( + root_dir = "/home/kamchatka-volcano/photos" + supported_files = [".jpg", ".png"] + [thumbnail_settings] + max_width = 256 + max_height = 256 + )"); + //At this point your config is ready to use + std::cout << "Launching PhotoViewer in directory " << cfg.rootDir << std::endl; + return 0; +} +``` ### Supported formats - -Internally, the `figcone` library works on a tree-like structure provided by the [`figcone_tree`](https://github.com/kamchatka-volcano/figcone_tree) library, and it is not aware of different configuration formats. The user needs to provide a parser implementing the `figcone_tree::IParser` interface to convert a configuration file to a tree structure based on the `figcone_tree::TreeNode` class. It is also possible to create a `figcone` compatible parser adapter that transforms the parsing result of some 3rd party configuration parsing library to a tree using `figcone_tree::TreeNode`. Five such adapters for popular configuration formats are included in `figcone`, and are fetched and built into a static library called `figcone_formats` which is automatically configured and linked by `figcone`'s CMake configuration. An obscure configuration format called [`shoal`](https://github.com/kamchatka-volcano/figcone_shoal), which was designed by the author of `figcone`, is also available and can be used as an example of an original parser implementation that is compatible with `figcone`. + +Internally, the `figcone` library works on a tree-like structure provided by +the [`figcone_tree`](https://github.com/kamchatka-volcano/figcone_tree) library, and it is not aware of different +configuration formats. The user needs to provide a parser implementing the `figcone_tree::IParser` interface to convert +a configuration file to a tree structure based on the `figcone_tree::TreeNode` class. It is also possible to create +a `figcone` compatible parser adapter that transforms the parsing result of some 3rd party configuration parsing library +to a tree using `figcone_tree::TreeNode`. Five such adapters for popular configuration formats are included +in `figcone`, and are fetched and built into a static library called `figcone_formats` which is automatically configured +and linked by `figcone`'s CMake configuration. An obscure configuration format +called [`shoal`](https://github.com/kamchatka-volcano/figcone_shoal), which was designed by the author of `figcone`, is +also available and can be used as an example of an original parser implementation that is compatible with `figcone`. Let's increase the complexity of our example config to demonstrate how configuration elements work with each format: #### demo.h + ```C++ ///examples/demo.h /// #pragma once #include -#include //enables macros without FIGCONE_ prefix #include #include #include -struct ThumbnailCfg : public figcone::Config -{ - PARAM(enabled, bool)(true); - PARAM(maxWidth, int); - PARAM(maxHeight, int); +struct ThumbnailCfg { + bool enabled = true; + int maxWidth; + int maxHeight; + + using traits = figcone::FieldTraits< + figcone::OptionalField<&ThumbnailCfg::enabled>>; }; -struct HostCfg : public figcone::Config{ - PARAM(ip, std::string); - PARAM(port, int); +struct HostCfg { + std::string ip; + int port; }; -struct SharedAlbumCfg : public figcone::Config{ - PARAM(dir, std::filesystem::path); - PARAM(name, std::string); - NODELIST(hosts, std::vector)(); +struct SharedAlbumCfg { + std::filesystem::path dir; + std::string name; + std::vector hosts; + + using traits = figcone::FieldTraits< + figcone::OptionalField<&SharedAlbumCfg::hosts>>; }; -struct PhotoViewerCfg : public figcone::Config{ - PARAM(rootDir, std::filesystem::path); - PARAMLIST(supportedFiles, std::vector); - NODE(thumbnails, ThumbnailCfg); - COPY_NODELIST(sharedAlbums, std::vector)(); - using StringMap = std::map; - DICT(envVars, StringMap)(); +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnails; + std::vector sharedAlbums; + std::map envVars; + + using traits = figcone::FieldTraits< + figcone::OptionalField<&PhotoViewerCfg::envVars>, + figcone::OptionalField<&PhotoViewerCfg::sharedAlbums>, + figcone::CopyNodeListField<&PhotoViewerCfg::sharedAlbums>>; }; ``` @@ -589,7 +739,7 @@ To create a parser compatible with `figcone`, you will need to use the [`figcone class DemoTreeProvider : public figcone::IParser { - figcone::TreeNode parse(std::istream& stream) final + figcone::Tree parse(std::istream& stream) final { auto tree = figcone::makeTreeRoot(); tree->asItem().addParam("rootDir", "~/Photos"); @@ -632,7 +782,6 @@ int main() auto parser = DemoTreeProvider{}; auto cfg = cfgReader.read("", parser); std::cout << "Launching PhotoViewer in directory " << cfg.rootDir; - return 0; } ``` @@ -642,23 +791,22 @@ To use user-defined types in your config, it's necessary to add a specialization Let's replace the HostCfg config structure with a parameter of type Host that is stored in the config as a string `"ipAddress:port"`. ```C++ -///examples/ex03.cpp +///examples_static_refl/ex03_static_refl.cpp /// -#include -#include //enables macros without FIGCONE_ prefix +#include #include #include #include #include -struct Host{ +struct Host { std::string ip; int port; }; -namespace figcone{ +namespace figcone { template<> -struct StringConverter{ +struct StringConverter { static std::optional fromString(const std::string& data) { auto delimPos = data.find(':'); @@ -670,18 +818,21 @@ struct StringConverter{ return host; } }; -} -struct SharedAlbumCfg : public figcone::Config{ - PARAM(dir, std::filesystem::path); - PARAM(name, std::string); - PARAMLIST(hosts, std::vector)(); +} //namespace figcone + +struct SharedAlbumCfg { + std::filesystem::path dir; + std::string name; + std::vector hosts; }; -struct PhotoViewerCfg : public figcone::Config{ - PARAM(rootDir, std::filesystem::path); - PARAMLIST(supportedFiles, std::vector); - COPY_NODELIST(sharedAlbums, std::vector)(); - using StringMap = std::map; - DICT(envVars, StringMap)(); +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + std::vector sharedAlbums; + std::map envVars; + + using traits = figcone::FieldTraits< + figcone::OptionalField<&PhotoViewerCfg::envVars>>; }; int main() @@ -750,7 +901,7 @@ Provide a template definition with the `void operator()(figcone::FieldType, const std::string& fieldName, const figcone::StreamPosition&)` callable signature to apply the new behavior to all config levels: -``` +```c++ namespace figcone { template @@ -766,7 +917,7 @@ struct UnregisteredFieldHandler { Or create a specialization of `UnregisteredFieldHandler` with a specific node type: -``` +```c++ namespace figcone { template<> @@ -783,7 +934,7 @@ struct UnregisteredFieldHandler { Instead of suppressing all errors of this kind, it's possible to make more targeted adjustments. For example, let's allow SharedAlbumCfg to have unregistered parameters but forbid the unregistered subnodes: -``` +```c++ namespace figcone { template<> @@ -803,10 +954,12 @@ struct UnregisteredFieldHandler { ### Validators Processed config parameters and nodes can be validated by registering constraint-checking functions or callable objects. -The signature must be compatible with `void (const T&)` where `T` is the type of a validated config structure +The signature must be compatible with `void (const T&)` where `T` is the type of validated config structure field `T value` or an optional field `figcone::optional` (validators of optional fields aren't invoked if they are empty). -If the option's value is invalid, the validator must throw a `figcone::ValidationError` exception: +If the option's value is invalid, the validator must throw a `figcone::ValidationError` exception. + +#### Runtime reflection validators ```c++ struct Cfg : figcone::Config<>{ @@ -822,8 +975,7 @@ Let's improve the PhotoViewer program by checking that `rootDir` path exists and ```c++ ///examples/ex04.cpp /// -#include -#include +#include #include #include #include @@ -876,27 +1028,110 @@ int main() Now the `read` method will throw an exception if configuration provides invalid `rootDir` or `supportedFiles` parameters. +#### Static reflection validators + +```c++ +struct NotNegative{ + void operator()(int paramValue) + { + if (paramValue < 0) + throw figcone::ValidationError{"value can't be negative."}; + } +}; + +struct Cfg { + int number; + + using traits=figcone::FieldTraits< + figcone::ValidatedField<&Cfg::number, NotNegative> + >; +}; +``` + +Let's rewrite the PhotoViewer program again, this time using compile time reflection interface: + +```c++ +///examples_static_refl/ex04_static_refl.cpp +/// +#include +#include +#include +#include +#include + +struct NotEmpty { + template + void operator()(const TList& list) + { + if (!list.empty()) + throw figcone::ValidationError{"can't be empty."}; + } +}; + +struct PathExists { + void operator()(const std::filesystem::path& path) + { + if (!std::filesystem::exists(path)) + throw figcone::ValidationError{"a path must exist"}; + } +}; + +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + std::map envVars; + + using traits = figcone::FieldTraits< + figcone::ValidatedField<&PhotoViewerCfg::rootDir, PathExists>, + figcone::ValidatedField<&PhotoViewerCfg::supportedFiles, NotEmpty>, + figcone::OptionalField<&PhotoViewerCfg::envVars>>; +}; + +int main() +{ + try { + auto cfgReader = figcone::ConfigReader{}; + auto cfg = cfgReader.readYaml(R"( + rootDir: ~/Photos + supportedFiles: [] + )"); + + std::cout << "Launching PhotoViewer in directory " << cfg.rootDir << std::endl; + + if (!cfg.supportedFiles.empty()) + std::cout << "Supported files:" << std::endl; + for (const auto& file : cfg.supportedFiles) + std::cout << " " << file << std::endl; + + return 0; + } + catch (const figcone::ConfigError& e) { + std::cout << "Config error:" << e.what(); + return 1; + } +} +``` + ### Post-processors If you need to modify or validate the config object that is produced by `figcone::ConfigReader`, you can register the necessary action by creating a specialization of the `figcone::PostProcessor` class template: ```cpp -///examples/ex05.cpp +///examples_static_refl/ex05_static_refl.cpp /// -struct ThumbnailCfg : public figcone::Config -{ - FIGCONE_PARAM(maxWidth, int); - FIGCONE_PARAM(maxHeight, int); +struct ThumbnailCfg { + int maxWidth; + int maxHeight; }; -struct PhotoViewerCfg : public figcone::Config{ - FIGCONE_PARAM(rootDir, std::filesystem::path); - FIGCONE_PARAMLIST(supportedFiles, std::vector); - FIGCONE_NODE(thumbnailSettings, ThumbnailCfg); +struct PhotoViewerCfg { + std::filesystem::path rootDir; + std::vector supportedFiles; + ThumbnailCfg thumbnailSettings; }; -namespace figcone{ +namespace figcone { template<> void PostProcessor::operator()(PhotoViewerCfg& cfg) { @@ -964,7 +1199,7 @@ cd build/tests && ctest ## Building examples ``` cd figcone -cmake -S . -B build -DENABLE_EXAMPLES=ON -DFIGCONE_USE_NAMEOF=ON +cmake -S . -B build -DENABLE_EXAMPLES=ON cmake --build build cd build/examples ```