{"id":15048050,"url":"https://github.com/kamchatka-volcano/figcone","last_synced_at":"2025-04-06T19:11:26.159Z","repository":{"id":40560070,"uuid":"483007752","full_name":"kamchatka-volcano/figcone","owner":"kamchatka-volcano","description":"Read JSON, YAML, TOML, XML or INI configuration by declaring a struct","archived":false,"fork":false,"pushed_at":"2025-02-28T22:06:37.000Z","size":394,"stargazers_count":111,"open_issues_count":2,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-30T18:08:51.928Z","etag":null,"topics":["configuration-parser","cpp17","cpp20","ini","json","pfr","shoal","static-reflection","toml","xml","yaml"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"ms-pl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kamchatka-volcano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-18T21:51:14.000Z","updated_at":"2025-03-20T08:32:27.000Z","dependencies_parsed_at":"2024-01-06T19:30:51.240Z","dependency_job_id":"d0beb96e-c69d-4e63-823b-c64ab67b7343","html_url":"https://github.com/kamchatka-volcano/figcone","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Ffigcone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Ffigcone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Ffigcone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Ffigcone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kamchatka-volcano","download_url":"https://codeload.github.com/kamchatka-volcano/figcone/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247535517,"owners_count":20954576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration-parser","cpp17","cpp20","ini","json","pfr","shoal","static-reflection","toml","xml","yaml"],"created_at":"2024-09-24T21:07:31.731Z","updated_at":"2025-04-06T19:11:26.141Z","avatar_url":"https://github.com/kamchatka-volcano.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"128\" src=\"doc/logo.jpg\"/\u003e  \n\u003c/p\u003e\n\n[![build \u0026 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)\n\n`figcone` - is a C++17 / C++20 library, providing a convenient declarative interface for configuration parsers and\nbuilt-in support for reading `JSON`, `YAML`, `TOML`, `XML`, `INI` and `shoal` config files. To use it, create a\nconfiguration schema by declaring a structure for each level of your config file and load it by calling a method,\nmatching the preferred configuration format:\n\n```C++\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n\nstruct ThumbnailCfg {\n    int maxWidth;\n    int maxHeight;\n};\n\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnailSettings;\n};\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readToml\u003cPhotoViewerCfg\u003e(R\"(\n        rootDir = \"~/Photos\"\n        supportedFiles = [\".jpg\", \".png\"]\n        [thumbnailSettings]\n          maxWidth = 256\n          maxHeight = 256\n    )\");\n    //At this point your config is ready to use\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n}\n```\n\nThis example uses the static reflection interface based on the [`pfr`](https://github.com/apolukhin/pfr_non_boost)\nlibrary. It requires C++20 and only works with\naggregate initializable structures. On C++17, the runtime reflection solution originally developed with `figcone` is\nused to register configuration fields:\n\n```C++\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n\nstruct ThumbnailCfg : public figcone::Config\n{\n    int maxWidth = param\u003c\u0026ThumbnailCfg::maxWidth\u003e();\n    int maxHeight = param\u003c\u0026ThumbnailCfg::maxHeight\u003e();\n};\n\nstruct PhotoViewerCfg : public figcone::Config{\n    //alternatively config fields can be created with macros:\n    FIGCONE_PARAM(rootDir, std::filesystem::path);\n    FIGCONE_PARAMLIST(supportedFiles, std::vector\u003cstd::string\u003e);\n    FIGCONE_NODE(thumbnailSettings, ThumbnailCfg);\n};\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readToml\u003cPhotoViewerCfg\u003e(R\"(\n        rootDir = \"~/Photos\"\n        supportedFiles = [\".jpg\", \".png\"]\n        [thumbnailSettings]\n          maxWidth = 256\n          maxHeight = 256\n    )\");\n    //At this point your config is ready to use\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n}\n```\n\n\n## Table of Contents\n\n* [Usage](#usage)\n    * [Config structure for runtime reflection (C++17)](#config-structure-for-runtime-reflection-c17)\n        * [Supporting non-aggregate config structures](#supporting-non-aggregate-config-structures)\n        * [Registration without macros](#registration-without-macros)\n    * [Config structure for static reflection (C++20)](#config-structure-for-static-reflection-c20)\n    * [Supported formats](#supported-formats)\n        * [JSON](#json)\n        * [YAML](#yaml)\n        * [TOML](#toml)\n        * [XML](#xml)\n        * [INI](#ini)\n        * [shoal](#shoal)\n    * [Creation of figcone-compatible parsers](#creation-of-figcone-compatible-parsers)\n    * [User defined types](#user-defined-types)\n    * [Unregistered fields handling](#unregistered-fields-handling)\n    * [Validators](#validators)\n        * [Runtime reflection validators](#runtime-reflection-validators)\n        * [Static reflection validators](#static-reflection-validators)\n    * [Post-processors](#post-processors)\n* [Installation](#installation)\n* [Running tests](#running-tests)\n* [Building examples](#building-examples)   \n* [License](#license)\n\n## Usage\n\n### Config structure for runtime reflection (C++17)\n\nTo register configuration structure, subclass `figcone::Config` and declare fields using the following macros:\n\n- **FIGCONE_PARAM(`name`, `type`)** - creates a `type name;` config field and registers it in the parser.\n- **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.\n- **FIGCONE_NODE(`name`, `type`)** - creates a `type name;` config field for a nested configuration structure and registers it in the parser. The type of the name field must be a subclass of `figcone::Config`.\n- **FIGCONE_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`.\n- **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.\n- **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`  \nThe 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:\n\n ```c++\n    using StringMap = std::map\u003cstd::string, std::string\u003e;\n    FIGCONE_DICT(testDict, StringMap);\n ```\n\nNotes:\n\n- All config entities listed above provide the parenthesis operator `()` which sets the default value and makes this\n  config field optional. This means that the field can be omitted from the configuration file without raising an error.\n  The empty `operator ()` makes a field's value default initialized, otherwise the passed parameters are used for\n  initialization. `FIGCONE_NODE`, `FIGCONE_NODELIST`, and `FIGCONE_COPY_NODELIST` only support default initialization.\n- It is also possible to make any config field optional by placing it in `figcone::optional` (a `std::optional`-like\n  wrapper with a similar interface). If a value for this field is missing from the config file, the field remains\n  uninitialized and no error occurs.\n- Types used for config parameters must be default constructible and copyable.\n\n#### Supporting non-aggregate config structures\n\nRuntime reflection interface of `figcone` relies on aggregate initialization of user-provided structures. If your config\nobject needs to contain private data or virtual functions, it becomes a non-aggregate type. In this case, you must use\nthe following `using` declaration to inherit `figcone::Config`'s constructors: `using Config::Config;`\n\n```cpp\nstruct PhotoViewerCfg : public figcone::Config\n{\n    using Config::Config;\n    virtual ~PhotoViewerCfg() = default; //virtual destructor makes PhotoViewerCfg non-aggregate\n    FIGCONE_PARAM(rootDir, std::filesystem::path);\n    FIGCONE_PARAMLIST(supportedFiles, std::vector\u003cstd::string\u003e);\n    FIGCONE_NODE(thumbnailSettings, ThumbnailCfg);\n};\n```\n\n#### Registration without macros\n\nRuntime reflection interface of `figcone` can be used without macros, as every configuration entity described earlier\ncan be registered with the similarly named `figcone::Config`'s member templates:\n\n```c++\n    struct Cfg : public figcone::Config{\n        int testParam                               = param\u003c\u0026Cfg::testParam\u003e();\n        int testParam2                              = param\u003c\u0026Cfg::testParam2\u003e()(100);\n        figcone::optional\u003cint\u003e testParam3           = param\u003c\u0026Cfg::testParam3\u003e();\n        std::vector\u003cdouble\u003e testParamList           = paramList\u003c\u0026Cfg::testParamList\u003e();\n        TestNode testNode                           = node\u003c\u0026Cfg::testNode\u003e();\n        figcone::optional\u003cTestNode\u003e testNode2       = node\u003c\u0026Cfg::testNode2\u003e();\n        std::vector\u003cTestNode\u003e testNodeList          = nodeList\u003c\u0026Cfg::testNodeList\u003e();\n        std::vector\u003cTestNode\u003e copyTestNodeList      = copyNodeList\u003c\u0026Cfg::copyTestNodeList\u003e();\n        std::map\u003cstd::string, std::string\u003e testDict = dict\u003c\u0026Cfg::testDict\u003e();\n    };\n```\n\nInternally, these methods use the [`nameof`](https://github.com/Neargye/nameof) library to get config fields' names as\nstrings. Note that nameof relies on non-standard functionality of C++ compilers, so if you don't like it, you can\nuse `figcone` without it by providing names for config fields yourself:\n\n```c++\n    struct Cfg : public figcone::Config{\n        int testParam                          = param\u003c\u0026Cfg::testParam\u003e(\"testParam\");\n        int testParam2                         = param\u003c\u0026Cfg::testParam2\u003e(\"testParam2\")(100);\n        figcone::optional\u003cint\u003e testParam3      = param\u003c\u0026Cfg::testParam3\u003e(\"testParam3\");\n        std::vector\u003cdouble\u003e testParamList      = paramList\u003c\u0026Cfg::testParamList\u003e(\"testParamList\");\n        TestNode testNode                      = node\u003c\u0026Cfg::testNode\u003e(\"testNode\");\n        figcone::optional\u003cTestNode\u003e testNode2  = node\u003c\u0026Cfg::testNode2\u003e(\"testNode2\");\n        std::vector\u003cTestNode\u003e testNodeList     = nodeList\u003c\u0026Cfg::testNodeList\u003e(\"testNodeList\");\n        std::vector\u003cTestNode\u003e copyTestNodeList = copyNodeList\u003c\u0026Cfg::copyTestNodeList\u003e(\"copyTestNodeList\");\n        std::map\u003cstd::string, std::string\u003e testDict = dict\u003c\u0026Cfg::testDict\u003e(\"testDict\");\n    };\n```\n\nPlease note that on the MSVC compiler, the `nameof` features used by `figcone` require the C++20 standard. This is\nhandled automatically by CMake configuration if MSVC is your default compiler. Otherwise, you will need to enable the\nC++20 standard manually.\n\nConfig structures declared using the macro-free methods are fully compatible with all of `figcone`'s functionality.\nExamples in the documentation use registration with macros, as it is the least verbose method.\n\n### Config structure for static reflection (C++20)\n\nStatic reflection provided by the [`pfr`](https://github.com/apolukhin/pfr_non_boost) library allows us to register\nconfiguration by using aggregate structures\nwithout any base class and macros. Let's return to the example from the beginning of this document:\n\n```c++\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnailSettings;\n};\n```\n\nAll field types and names are registered during compile time. In this example, `rootDir` is registered as a\nparameter, `supportedFiles` as a parameter list, and `thumbnailSettings` as a node.  \nIf a field can be converted to a string (by a conversion operator, std::stringstream, or figcone::StringConverter—which\nwill be discussed later), it is registered as a parameter; otherwise, it is registered as a node. The same rule applies\nto the container elements.\n\nTo create node or parameter lists, use any sequence container that supports the `emplace_back` operation, such\nas `vector`, `deque`, or `list` from the STL.   \nTo create dictionaries, use any associative container that supports the emplace operation, such as `map`\nor `unordered_map` from the STL. The key type of the map must be `std::string`.\n\nOptional fields are created with `std::optional`; there's no need to use `figcone::optional`. If a field has a default\nvalue, it's not ergonomic to place it in `std::optional` just to specify that it can be omitted from the config file. An\nalternative way to make a field optional is using the `figcone::OptionalField` trait and placing it in\nthe `figcone::FieldTraits` type list. Let's make `supportedFiles` from the previous example optional to see how it's\ndone:\n\n```c++\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnailSettings;\n    \n    using traits=figcone::FieldTraits\u003c\n      figcone::OptionalField\u003c\u0026PhotoViewerCfg::supportedFiles\u003e     \n    \u003e;\n};\n```\n\nAnother trait is `figcone::CopyNodeListField`. It enables the use of a copy node list functionality. The first element\nof such a list acts as a template for the other elements. This means that all unspecified parameters of the second and\nfollowing elements will be copied from the first element without raising a parsing error for missing parameters (it's\nthe same field type as `FIGCONE_COPYNODELIST` from the runtime reflection interface).\n\nLet's make `thumbnailSettings` a copy node list:\n\n```c++\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    std::vector\u003cThumbnailCfg\u003e thumbnailSettings;\n    \n    using traits=figcone::FieldTraits\u003c\n      figcone::OptionalField\u003c\u0026PhotoViewerCfg::supportedFiles\u003e,\n      figcone::CopyNodeListField\u003c\u0026PhotoViewerCfg::thumbnailSettings\u003e\n    \u003e;\n};\n```\n\nSadly, using such trait lists is currently the only possible way to add additional information about config fields. If\nyou have complex configuration that needs to have many traits, it might make sense to prefer the runtime reflection\ninterface to keep the code more concise.\n\n### Name format\n\nYou do not need to change your code style when declaring config fields. `camelCase`, `snake_case`, and `PascalCase`\nnames are supported, and can be converted to the format used by parameter names in the config file. To do this, specify\nthe configuration names format with the `figcone::NameFormat` enum by passing its value to the `figcone::ConfigReader`\ntemplate argument.\n\nTo demonstrate it, let's change our PhotoViewer example to use snake_case names in the configuration:\n\n```C++\n///examples_static_refl/ex02_static_refl.cpp\n///\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n\nstruct ThumbnailCfg {\n    int maxWidth;\n    int maxHeight;\n};\n\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnailSettings;\n};\n\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader\u003cfigcone::NameFormat::SnakeCase\u003e{};\n    auto cfg = cfgReader.readToml\u003cPhotoViewerCfg\u003e(R\"(\n        root_dir = \"/home/kamchatka-volcano/photos\"\n        supported_files = [\".jpg\", \".png\"]\n        [thumbnail_settings]\n          max_width = 256\n          max_height = 256\n    )\");\n    //At this point your config is ready to use\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\n\n### Supported formats\n\nInternally, the `figcone` library works on a tree-like structure provided by\nthe [`figcone_tree`](https://github.com/kamchatka-volcano/figcone_tree) library, and it is not aware of different\nconfiguration formats. The user needs to provide a parser implementing the `figcone_tree::IParser` interface to convert\na configuration file to a tree structure based on the `figcone_tree::TreeNode` class. It is also possible to create\na `figcone` compatible parser adapter that transforms the parsing result of some 3rd party configuration parsing library\nto a tree using `figcone_tree::TreeNode`. Five such adapters for popular configuration formats are included\nin `figcone`, and are fetched and built into a static library called `figcone_formats` which is automatically configured\nand linked by `figcone`'s CMake configuration. An obscure configuration format\ncalled [`shoal`](https://github.com/kamchatka-volcano/figcone_shoal), which was designed by the author of `figcone`, is\nalso available and can be used as an example of an original parser implementation that is compatible with `figcone`.\n\nLet's increase the complexity of our example config to demonstrate how configuration elements work with each format:\n\n#### demo.h\n\n```C++\n///examples/demo.h\n///\n#pragma once\n#include \u003cfigcone/config.h\u003e\n#include \u003cstring\u003e\n#include \u003cvector\u003e\n#include \u003cmap\u003e\n\nstruct ThumbnailCfg {\n    bool enabled = true;\n    int maxWidth;\n    int maxHeight;\n\n    using traits = figcone::FieldTraits\u003c\n            figcone::OptionalField\u003c\u0026ThumbnailCfg::enabled\u003e\u003e;\n};\nstruct HostCfg {\n    std::string ip;\n    int port;\n};\nstruct SharedAlbumCfg {\n    std::filesystem::path dir;\n    std::string name;\n    std::vector\u003cHostCfg\u003e hosts;\n\n    using traits = figcone::FieldTraits\u003c\n            figcone::OptionalField\u003c\u0026SharedAlbumCfg::hosts\u003e\u003e;\n};\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnails;\n    std::vector\u003cSharedAlbumCfg\u003e sharedAlbums;\n    std::map\u003cstd::string, std::string\u003e envVars;\n\n    using traits = figcone::FieldTraits\u003c\n            figcone::OptionalField\u003c\u0026PhotoViewerCfg::envVars\u003e,\n            figcone::OptionalField\u003c\u0026PhotoViewerCfg::sharedAlbums\u003e,\n            figcone::CopyNodeListField\u003c\u0026PhotoViewerCfg::sharedAlbums\u003e\u003e;\n};\n```\n\n#### JSON\n\nJSON support is provided by [`nlohmann/json`](https://github.com/nlohmann/json) library which is fetched and adapted to the `figcone` interface by the [`figcone_json`](https://github.com/kamchatka-volcano/figcone_json) library.\n\nA JSON config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:\n\n`demo.json`\n```json\n{\n  \"rootDir\": \"~/Photos\",\n  \"supportedFiles\": [\n    \".jpg\",\n    \"png\"\n  ],\n  \"thumbnails\": {\n    \"enabled\": \"1\",\n    \"maxWidth\": \"128\",\n    \"maxHeight\": \"128\"\n  },\n  \"sharedAlbums\": [\n    {\n      \"dir\": \"summer_2019\",\n      \"name\": \"Summer (2019)\",\n      \"hosts\" : [{\"ip\" : \"127.0.0.1\", \"port\" : \"8080\" }]\n    },\n    {\n      \"dir\": \"misc\",\n      \"name\": \"Misc\"\n    }\n  ],\n  \"envVars\": {\n    \"DISPLAY\" : \"0.1\"\n  }\n}\n```\n\n```C++\n///examples/demo_json.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readJsonFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.json\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\n\nJSON configurations that have a node list in its root are supported. Pass `figcone::RootType::NodeList` as the second\ntemplate argument of the used read function:\n\n```C++\n///examples/demo_json_root_list.cpp\n///\n#include \"demo.h\"\n#include \"print_demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfgList = cfgReader.readJsonFile\u003cPhotoViewerCfg, figcone::RootType::NodeList\u003e(\n            std::filesystem::canonical(\"../../examples/demo_root_list.json\"));\n    for (const auto\u0026 cfg : cfgList) {\n        std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n        printDemoConfig(cfg);\n        std::cout \u003c\u003c \"---\" \u003c\u003c std::endl;\n    }\n}\n\n```\n\n#### YAML\n\nYAML support is provided by the [`rapidyaml`](https://github.com/biojppm/rapidyaml) library which is fetched and adapted\nto the `figcone` interface by the [`figcone_yaml`](https://github.com/kamchatka-volcano/figcone_yaml) library.\n\nA YAML config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:  \n`demo.yaml`\n\n```yaml\n  rootDir: ~/Photos\n  supportedFiles: [ \".jpg\", \"png\"]\n  thumbnails:\n    enabled: 1\n    maxWidth: 128\n    maxHeight: 128\n  \n  sharedAlbums:\n    -\n      dir: \"summer_2019\"\n      name: \"Summer (2019)\"\n      hosts: \n        -\n          ip: \"127.0.0.1\"\n          port: 8080\n    -    \n      dir: \"misc\"\n      name: \"Misc\"      \n  envVars: \n    DISPLAY : \"0.1\"\n```\n```c++\n///examples/demo_yaml.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readYamlFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.yaml\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\n\nYAML configurations that have a node list in its root are supported. Pass `figcone::RootType::NodeList` as the second\ntemplate argument of the used read function:\n\n```c++\n///examples/demo_yaml_root_list.cpp\n///\n#include \"demo.h\"\n#include \"print_demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfgList = cfgReader.readYamlFile\u003cPhotoViewerCfg, figcone::RootType::NodeList\u003e(\n            std::filesystem::canonical(\"../../examples/demo_root_list.yaml\"));\n    for (const auto\u0026 cfg : cfgList) {\n        std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n        printDemoConfig(cfg);\n        std::cout \u003c\u003c \"---\" \u003c\u003c std::endl;\n    }\n}\n```\n\n#### TOML\n\nTOML support is provided by the [`toml11`](https://github.com/ToruNiina/toml11) library which is fetched and adapted to\nthe `figcone` interface by the [`figcone_toml`](https://github.com/kamchatka-volcano/figcone_toml) library.\n\nA TOML config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:  \n`demo.toml`\n\n```toml\n  rootDir = \"~/Photos\"\nsupportedFiles = [\".jpg\", \"png\"]\n  [thumbnails]\n    enabled = 1\n    maxWidth = 128\n    maxHeight = 128\n  \n  [[sharedAlbums]]\n    dir  = \"summer_2019\"\n    name = \"Summer (2019)\"\n    [[sharedAlbums.hosts]]\n      ip = \"127.0.0.1\"\n      port = 8080 \n  [[sharedAlbums]]\n      dir  = \"misc\"\n      name = \"Misc\"      \n  [envVars] \n    DISPLAY = \"0.1\"\n```\n```c++\n///examples/demo_toml.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readTomlFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.toml\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\nNotes:\n* TOML nested arrays aren't supported, as they can't be represented within `figcone` configuration tree.\n* TOML datetime types can be used in the config by including `\u003cfigcone/format/toml/datetime.h\u003e` header:\n```C++\n#include \u003cfigcone/config.h\u003e\n#include \u003cfigcone/format/toml/datetime.h\u003e\n\nstruct Cfg: figcone::Config\n{   \n    FICONE_PARAM(date, figcone::toml::DateTimePoint);\n    FICONE_PARAM(time, figcone::toml::TimeDuration);\n};\n```\n\n`figcone::toml::DateTimePoint` is used to read TOML `local_date (2018-12-23)`, \n`local_datetime (2018-12-23T12:30:00)` and `offset_datetime (2018-12-23T12:30:00+09:30)` values.  \n`figcone::toml::TimeDuration` is used to read TOML `time (12:30:00)` values.\n\n\n#### XML\n\nXML support is provided by the [`rapidxml`](https://github.com/dwd/rapidxml) library which is fetched and adapted to the `figcone` interface by the [`figcone_xml`](https://github.com/kamchatka-volcano/figcone_xml) library.\n\nAn XML config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:  \n`demo.xml`\n```xml\n\u003croot rootDir=\"~/Photos\"\n      supportedFiles = \"[ '.jpg', 'png']\"\u003e\n    \u003cthumbnails enabled=\"1\"\n                maxWidth=\"128\"\n                maxHeight=\"128\"/\u003e\n    \u003csharedAlbums\u003e\n        \u003calbum dir=\"summer_2019\"\n               name=\"Summer (2019)\"\u003e\n            \u003chosts\u003e\n                \u003chost ip=\"127.0.0.1\"\n                      port=\"8080\"/\u003e\n            \u003c/hosts\u003e\n        \u003c/album\u003e\n        \u003calbum dir=\"misc\"\n               name=\"Misc\"/\u003e\n    \u003c/sharedAlbums\u003e\n    \u003cenvVars DISPLAY=\"0.1\"/\u003e\n\u003c/root\u003e\n```\n\n```c++\n///examples/demo_xml.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readXmlFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.xml\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\nNotes:\n\n* Node list tags can't contain attributes and all its children tags representing node list elements must use the same\n  name.\n* Parameter lists are stored in attributes and have the format: `[value1, value2, ...]`\n\n#### INI\n\nINI support is provided by the [`inifile-cpp`](https://github.com/Rookfighter/inifile-cpp) library which is fetched and adapted to the `figcone` interface by the [`figcone_ini`](https://github.com/kamchatka-volcano/figcone_ini) library.\n\nAn INI config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:  \n`demo.ini`\n```ini\nrootDir = \"~/Photos\"\nsupportedFiles = [\".jpg\", \"png\"]\n[thumbnails]\n  enabled = 1\n  maxWidth = 128\n  maxHeight = 12\n[sharedAlbums.0]\n  dir  = \"summer_2019\"\n  name = \"Summer (2019)\"\n  [sharedAlbums.0.hosts.0]\n    ip = \"127.0.0.1\"\n    port = 8080\n[sharedAlbums.1]\n  dir  = \"misc\"\n  name = \"Misc\"\n[envVars]\nDISPLAY = \"0.1\"\n```\n```c++\n///examples/demo_ini.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readIniFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.ini\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    return 0;\n}\n```\nNotes:\n* Nested structures can be created by using dotted ini section names listing the full hierarchy: `[grandparent.parent.child]`  \n* Node lists can be created by using indices in dotted ini section names, starting with zero for the first element: `[nodelist.0]`\n* Parameter lists have the format: `[value1, value2, ...]`\n* Multiline values aren't supported.\n\n#### shoal\n\n[`shoal`](https://github.com/kamchatka-volcano/shoal) support is provided by the [`figcone_shoal`](https://github.com/kamchatka-volcano/figcone_shoal) library.\n\nA shoal config that matches the configuration listed in [`demo.h`](#demoh) earlier, looks like this:  \n`demo.shoal`\n```\nrootDir = \"~/Photos\"\nsupportedFiles = [ \".jpg\", \"png\"]\n#thumbnails:\n  enabled = 1\n  maxWidth = 128\n  maxHeight = 128\n---\n\n#sharedAlbums:\n###\n  dir  = \"summer_2019\"\n  name = \"Summer (2019)\"\n  #hosts:\n  ###\n    ip = \"127.0.0.1\"\n    port = 8080\n  -\n###\n  dir  = \"misc\"\n  name = \"Misc\"\n---\n\n#envVars:\n  DISPLAY = \"0.1\"\n```\n```c++\n///examples/demo_shoal.cpp\n///\n#include \"demo.h\"\n#include \"print_demo.h\"\n#include \u003cfigcone/configreader.h\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readShoalFile\u003cPhotoViewerCfg\u003e(std::filesystem::canonical(\"../../examples/demo.shoal\"));\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory\" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n    printDemoConfig(cfg);\n    return 0;\n}\n```\n\n### Creation of figcone-compatible parsers\nTo create a parser compatible with `figcone`, you will need to use the [`figcone_tree`](https://github.com/kamchatka-volcano/figcone_tree) library, which provides all the necessary types and interfaces for this task. The parsing class should implement the `figcone::IParser` interface and return the result of the configuration parsing in the form of a tree-like structure, constructed using `figcone::TreeNode` and `figcone::TreeParam` objects. Let's demonstrate how to work with the `figcone_tree` library by creating a fake parser that provides a configuration tree for the demo structure listed in [`demo.h`](#demoh):\n\n```C++\n///examples/demo_parser.cpp\n///\n#include \"demo.h\"\n#include \u003cfigcone_tree/iparser.h\u003e\n#include \u003cfigcone_tree/tree.h\u003e\n#include \u003cfigcone_tree/errors.h\u003e\n#include \u003cfigcone/configreader.h\u003e\n\nclass DemoTreeProvider : public figcone::IParser\n{\n    figcone::Tree parse(std::istream\u0026 stream) final\n    {\n        auto tree = figcone::makeTreeRoot();\n        tree-\u003easItem().addParam(\"rootDir\", \"~/Photos\");\n        tree-\u003easItem().addParamList(\"supportedFiles\", {\".jpg\", \".png\"});\n\n        auto\u0026 thumbNode = tree-\u003easItem().addNode(\"thumbnails\");\n        thumbNode.asItem().addParam(\"enabled\", \"1\");\n        thumbNode.asItem().addParam(\"maxWidth\", \"128\");\n        thumbNode.asItem().addParam(\"maxHeight\", \"128\");\n\n        auto\u0026 albumsNodeList = tree-\u003easItem().addNodeList(\"sharedAlbums\");\n        auto\u0026 albumNode = albumsNodeList.asList().emplaceBack();\n        albumNode.asItem().addParam(\"dir\", \"summer_2019\");\n        albumNode.asItem().addParam(\"name\", \"Summer (2019)\");\n        auto\u0026 hostsNodeList = albumNode.asItem().addNodeList(\"hosts\");\n        auto\u0026 hostNode = hostsNodeList.asList().emplaceBack();\n        hostNode.asItem().addParam(\"ip\", \"127.0.0.1\");\n        hostNode.asItem().addParam(\"port\", \"80\");\n\n        //For error notifications use figcone::ConfigError exceptions\n        if (stream.bad())\n            throw figcone::ConfigError{\"stream error\"};\n\n        //Stream position information can be added to objects\n        auto pos = figcone::StreamPosition{13, 1};\n        auto\u0026 envVarsNode = tree-\u003easItem().addNode(\"envVars\", pos);\n        envVarsNode.asItem().addParam(\"DISPLAY\", \"0.1\");\n\n        //and to ConfigError exceptions\n        if (stream.fail())\n            throw figcone::ConfigError{\"stream error\", pos};\n\n        return tree;\n    }\n};\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto parser = DemoTreeProvider{};\n    auto cfg = cfgReader.read\u003cPhotoViewerCfg\u003e(\"\", parser);\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir;\n}\n```\n\n\n### User defined types\nTo use user-defined types in your config, it's necessary to add a specialization of the struct `figcone::StringConverter` and implement its static method `fromString`.   \nLet's replace the HostCfg config structure with a parameter of type Host that is stored in the config as a string `\"ipAddress:port\"`.\n\n```C++\n///examples_static_refl/ex03_static_refl.cpp\n///\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n#include \u003cmap\u003e\n\nstruct Host {\n    std::string ip;\n    int port;\n};\n\nnamespace figcone {\ntemplate\u003c\u003e\nstruct StringConverter\u003cHost\u003e {\n    static std::optional\u003cHost\u003e fromString(const std::string\u0026 data)\n    {\n        auto delimPos = data.find(':');\n        if (delimPos == std::string::npos)\n            return {};\n        auto host = Host{};\n        host.ip = data.substr(0, delimPos);\n        host.port = std::stoi(data.substr(delimPos + 1, data.size() - delimPos - 1));\n        return host;\n    }\n};\n} //namespace figcone\n\nstruct SharedAlbumCfg {\n    std::filesystem::path dir;\n    std::string name;\n    std::vector\u003cHost\u003e hosts;\n};\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    std::vector\u003cSharedAlbumCfg\u003e sharedAlbums;\n    std::map\u003cstd::string, std::string\u003e envVars;\n\n    using traits = figcone::FieldTraits\u003c\n            figcone::OptionalField\u003c\u0026PhotoViewerCfg::envVars\u003e\u003e;\n};\n\nint main()\n{\n    auto cfgReader = figcone::ConfigReader{};\n    auto cfg = cfgReader.readYaml\u003cPhotoViewerCfg\u003e(R\"(\n      rootDir: ~/Photos\n      supportedFiles: [ \".jpg\", \"png\"]\n      sharedAlbums:\n        -\n          dir: \"summer_2019\"\n          name: \"Summer 2019\"\n          hosts: [\"127.0.0.1:8080\"]\n    )\");\n\n    std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n\n    if (!cfg.supportedFiles.empty())\n        std::cout \u003c\u003c \"Supported files:\" \u003c\u003c std::endl;\n    for (const auto\u0026 file : cfg.supportedFiles)\n        std::cout \u003c\u003c \"  \" \u003c\u003c file \u003c\u003c std::endl;\n\n    if (!cfg.sharedAlbums.empty())\n        std::cout \u003c\u003c \"Shared albums:\" \u003c\u003c std::endl;\n    for (const auto\u0026 album : cfg.sharedAlbums){\n        std::cout \u003c\u003c \"  Album:\" \u003c\u003c album.name \u003c\u003c std::endl;\n        std::cout \u003c\u003c \"    Hosts:\" \u003c\u003c std::endl;\n        for (const auto\u0026 host : album.hosts)\n            std::cout \u003c\u003c \"      \" \u003c\u003c host.ip \u003c\u003c \":\" \u003c\u003c host.port \u003c\u003c std::endl;\n    }\n\n    return 0;\n}\n```\n\nTo provide additional information in the error message of the `StringConverter`, you can use\nthe `figcone::ValidationError` exception:\n\n```cpp\nnamespace figcone{\ntemplate\u003c\u003e\nstruct StringConverter\u003cHost\u003e{\n    static std::optional\u003cHost\u003e fromString(const std::string\u0026 data)\n    {\n        auto delimPos = data.find(':');\n        if (delimPos == std::string::npos)\n            throw ValidationError{\"the host parameter must be in the format 'ipAddress:port'\"};\n        auto host = Host{};\n        host.ip = data.substr(0, delimPos);\n        host.port = std::stoi(data.substr(delimPos + 1, data.size() - delimPos - 1));\n        return host;\n    }\n};\n}\n```\n\n### Unregistered fields handling\n\nBy default, `figcone` requires an exact match of the configuration file content with a registered configuration\nstructure. Besides any missing or wrongly typed data, errors are raised when some config fields are present in the file\nbut are not registered in `figcone`. This behavior is debatable; for example, it can make it impossible to store two\ndifferent apps' configurations in the same file. For this reason, error signaling for unregistered fields can be\nsuppressed by registering the necessary behavior with the `UnregisteredFieldHandler` template class.\n\nProvide a template definition with\nthe `void operator()(figcone::FieldType, const std::string\u0026 fieldName, const figcone::StreamPosition\u0026)` callable\nsignature to apply the new behavior to all config levels:\n\n```c++\nnamespace figcone {\n\ntemplate\u003ctypename T\u003e\nstruct UnregisteredFieldHandler {\n    void operator()(figcone::FieldType, const std::string\u0026, const figcone::StreamPosition\u0026) \n    {\n        //Don't do anything here to suppress errors for all unregistered fields\n    }\n};\n\n} //namespace figcone\n```\n\nOr create a specialization of `UnregisteredFieldHandler` with a specific node type:\n\n```c++\nnamespace figcone {\n\ntemplate\u003c\u003e\nstruct UnregisteredFieldHandler\u003cSharedAlbumCfg\u003e {\n    void operator()(figcone::FieldType, const std::string\u0026, const figcone::StreamPosition\u0026) \n    {\n        //Don't do anything here to suppress errors for all unregistered fields inside the SharedAlbumCfg node.\n    }\n};\n\n} //namespace figcone\n```\n\nInstead of suppressing all errors of this kind, it's possible to make more targeted adjustments. For example, let's\nallow SharedAlbumCfg to have unregistered parameters but forbid the unregistered subnodes:\n\n```c++\nnamespace figcone {\n\ntemplate\u003c\u003e\nstruct UnregisteredFieldHandler\u003cSharedAlbumCfg\u003e {\n    void operator()(figcone::FieldType fieldType, const std::string\u0026 fieldName, const figcone::StreamPosition\u0026 position) \n    {\n        if (fieldType == figcone::FieldType::Node)\n            throw ConfigError{\"Unknown node '\" + fieldName + \"'\", position};\n            \n        //Don't do anything for fieldType == figcone::FieldType::Param     \n    }\n};\n\n} //namespace figcone\n```\n\n### Validators\n\nProcessed config parameters and nodes can be validated by registering constraint-checking functions or callable objects.\nThe signature must be compatible with `void (const T\u0026)` where `T` is the type of validated config structure\nfield `T value` or an optional field `figcone::optional\u003cT\u003e` (validators of optional fields aren't invoked if they are\nempty).   \nIf the option's value is invalid, the validator must throw a `figcone::ValidationError` exception.\n\n#### Runtime reflection validators\n\n```c++\nstruct Cfg : figcone::Config\u003c\u003e{\n    FIGCONE_PARAM(number, int).ensure( \n        [](int paramValue){\n            if (paramValue \u003c 0)\n                throw figcone::ValidationError{\"value can't be negative.\"};\n        });\n};\n```\n\nLet's improve the PhotoViewer program by checking that `rootDir` path exists and `supportedFiles`  parameter list isn't empty:\n```c++\n///examples/ex04.cpp\n///\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n#include\u003cmap\u003e\n\nstruct NotEmpty{\n    template\u003ctypename TList\u003e\n    void operator()(const TList\u0026 list)\n    {\n        if (!list.empty())\n            throw figcone::ValidationError{\"can't be empty.\"};\n    }\n};\n\nstruct PhotoViewerCfg : public figcone::Config{\n    PARAM(rootDir, std::filesystem::path).ensure([](const auto\u0026 path) {\n        if (!std::filesystem::exists(path))\n            throw figcone::ValidationError{\"a path must exist\"};\n    });\n    PARAMLIST(supportedFiles, std::vector\u003cstd::string\u003e).ensure\u003cNotEmpty\u003e();\n    using StringMap = std::map\u003cstd::string, std::string\u003e;\n    DICT(envVars, StringMap)();\n};\n\nint main()\n{\n    try {\n        auto cfgReader = figcone::ConfigReader{};\n        auto cfg = cfgReader.readYaml\u003cPhotoViewerCfg\u003e(R\"(\n            rootDir: ~/Photos\n            supportedFiles: []\n        )\");\n\n        std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n\n        if (!cfg.supportedFiles.empty())\n            std::cout \u003c\u003c \"Supported files:\" \u003c\u003c std::endl;\n        for (const auto\u0026 file : cfg.supportedFiles)\n            std::cout \u003c\u003c \"  \" \u003c\u003c file \u003c\u003c std::endl;\n\n        return 0;\n    }\n    catch(const figcone::ConfigError\u0026 e){\n        std::cout \u003c\u003c \"Config error:\" \u003c\u003c e.what();\n        return 1;\n    }\n}\n```\n\nNow the `read` method will throw an exception if configuration provides invalid `rootDir` or `supportedFiles`\nparameters.\n\n#### Static reflection validators\n\n```c++\nstruct NotNegative{\n    void operator()(int paramValue)\n    {\n        if (paramValue \u003c 0)\n                throw figcone::ValidationError{\"value can't be negative.\"};\n    }\n};\n\nstruct Cfg {\n    int number;\n    \n    using traits=figcone::FieldTraits\u003c\n      figcone::ValidatedField\u003c\u0026Cfg::number, NotNegative\u003e\n    \u003e;  \n};\n```\n\nLet's rewrite the PhotoViewer program again, this time using compile time reflection interface:\n\n```c++\n///examples_static_refl/ex04_static_refl.cpp\n///\n#include \u003cfigcone/figcone.h\u003e\n#include \u003cfilesystem\u003e\n#include \u003ciostream\u003e\n#include \u003cmap\u003e\n#include \u003cvector\u003e\n\nstruct NotEmpty {\n    template\u003ctypename TList\u003e\n    void operator()(const TList\u0026 list)\n    {\n        if (!list.empty())\n            throw figcone::ValidationError{\"can't be empty.\"};\n    }\n};\n\nstruct PathExists {\n    void operator()(const std::filesystem::path\u0026 path)\n    {\n        if (!std::filesystem::exists(path))\n            throw figcone::ValidationError{\"a path must exist\"};\n    }\n};\n\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    std::map\u003cstd::string, std::string\u003e envVars;\n\n    using traits = figcone::FieldTraits\u003c\n            figcone::ValidatedField\u003c\u0026PhotoViewerCfg::rootDir, PathExists\u003e,\n            figcone::ValidatedField\u003c\u0026PhotoViewerCfg::supportedFiles, NotEmpty\u003e,\n            figcone::OptionalField\u003c\u0026PhotoViewerCfg::envVars\u003e\u003e;\n};\n\nint main()\n{\n    try {\n        auto cfgReader = figcone::ConfigReader{};\n        auto cfg = cfgReader.readYaml\u003cPhotoViewerCfg\u003e(R\"(\n            rootDir: ~/Photos\n            supportedFiles: []\n        )\");\n\n        std::cout \u003c\u003c \"Launching PhotoViewer in directory \" \u003c\u003c cfg.rootDir \u003c\u003c std::endl;\n\n        if (!cfg.supportedFiles.empty())\n            std::cout \u003c\u003c \"Supported files:\" \u003c\u003c std::endl;\n        for (const auto\u0026 file : cfg.supportedFiles)\n            std::cout \u003c\u003c \"  \" \u003c\u003c file \u003c\u003c std::endl;\n\n        return 0;\n    }\n    catch (const figcone::ConfigError\u0026 e) {\n        std::cout \u003c\u003c \"Config error:\" \u003c\u003c e.what();\n        return 1;\n    }\n}\n```\n\n### Post-processors\n\nIf you need to modify or validate the config object that is produced by `figcone::ConfigReader`, you can register\nthe necessary action by creating a specialization of the `figcone::PostProcessor` class template:\n\n```cpp\n///examples_static_refl/ex05_static_refl.cpp\n///\nstruct ThumbnailCfg {\n    int maxWidth;\n    int maxHeight;\n};\n\nstruct PhotoViewerCfg {\n    std::filesystem::path rootDir;\n    std::vector\u003cstd::string\u003e supportedFiles;\n    ThumbnailCfg thumbnailSettings;\n};\n\nnamespace figcone {\n    template\u003c\u003e\n    void PostProcessor\u003cPhotoViewerCfg\u003e::operator()(PhotoViewerCfg\u0026 cfg)\n    {\n        auto supportPng = std::find(cfg.supportedFiles.begin(), cfg.supportedFiles.end(), \".png\") != cfg.supportedFiles.end();\n        if (supportPng \u0026\u0026 cfg.thumbnailSettings.maxWidth \u003e 128)\n            throw ValidationError{\"thumbnail width can't be larger than 128px when png images are present\"};\n\n    }\n}\n}\n```\n\n## Installation\n\nDownload and link the library from your project's CMakeLists.txt:\n\n```\ncmake_minimum_required(VERSION 3.14)\n\ninclude(FetchContent)\nFetchContent_Declare(figcone\n    GIT_REPOSITORY \"https://github.com/kamchatka-volcano/figcone.git\"\n    GIT_TAG \"origin/master\"\n)\n#uncomment if you need to install figcone with your target\n#set(INSTALL_FIGCONE ON)\nFetchContent_MakeAvailable(figcone)\n\nadd_executable(${PROJECT_NAME})\ntarget_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone)\n```\n\nBy default `figcone` fetches all supported configuration format libraries. You can control this with CMake option `FIGCONE_USE_ALL`. You can also enable support for a single configuration format or a combination of formats  by setting the following options:\n  * `FIGCONE_USE_JSON` - fetches and configures the `figcone_json` library;\n  * `FIGCONE_USE_YAML` - fetches and configures the `figcone_yaml` library;\n  * `FIGCONE_USE_TOML` - fetches and configures the `figcone_toml` library;\n  * `FIGCONE_USE_XML` - fetches and configures the `figcone_xml` library;\n  * `FIGCONE_USE_INI` - fetches and configures the `figcone_ini` library;\n  * `FIGCONE_USE_SHOAL` - fetches and configures the `figcone_shoal` library;\n\nTo install the library system-wide, use the following commands:\n```\ngit clone https://github.com/kamchatka-volcano/figcone.git\ncd figcone\ncmake -S . -B build\ncmake --build build\ncmake --install build\n```\n\nAfter installation, you can use the `find_package()` command to make the installed library available inside your project:\n```\nfind_package(figcone 2.0.0 REQUIRED)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone)   \n```\n\n\n## Running tests\n```\ncd figcone\ncmake -S . -B build -DENABLE_TESTS=ON\ncmake --build build\ncd build/tests \u0026\u0026 ctest\n```\n\n## Building examples\n```\ncd figcone\ncmake -S . -B build -DENABLE_EXAMPLES=ON\ncmake --build build\ncd build/examples\n```\n\n## License\n`figcone` is licensed under the [MS-PL license](/LICENSE.md)  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Ffigcone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkamchatka-volcano%2Ffigcone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Ffigcone/lists"}