├── release_pack ├── CMakeLists.txt └── release.sh ├── include └── figcone_tree │ ├── iparser.h │ ├── stringconverter.h │ ├── streamposition.h │ ├── errors.h │ └── tree.h ├── external └── seal_lake ├── CMakeLists.txt ├── .github └── workflows │ ├── release.yml │ └── version_check.yml ├── README.md ├── .clang-format ├── LICENSE.md └── CMakePresets.json /release_pack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(figcone_tree VERSION 2.2.0 DESCRIPTION "C++17 interface library for creating configuration parsers compatible with figcone") 4 | include(seal_lake.cmake) 5 | 6 | SealLake_v040_HeaderOnlyLibrary( 7 | NAMESPACE figcone 8 | COMPILE_FEATURES cxx_std_17 9 | ) 10 | -------------------------------------------------------------------------------- /include/figcone_tree/iparser.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_TREE_IPARSER_H 2 | #define FIGCONE_TREE_IPARSER_H 3 | 4 | #include "tree.h" 5 | #include 6 | 7 | namespace figcone { 8 | class IParser { 9 | public: 10 | virtual ~IParser() = default; 11 | virtual Tree parse(std::istream& stream) = 0; 12 | }; 13 | 14 | } //namespace figcone 15 | 16 | #endif //FIGCONE_TREE_IPARSER_H 17 | -------------------------------------------------------------------------------- /include/figcone_tree/stringconverter.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_TREE_STRINGCONVERTER_H 2 | #define FIGCONE_TREE_STRINGCONVERTER_H 3 | 4 | #include "errors.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace figcone { 10 | 11 | template 12 | struct StringConverter; 13 | 14 | } //namespace figcone 15 | 16 | #endif //FIGCONE_TREE_STRINGCONVERTER_H 17 | -------------------------------------------------------------------------------- /release_pack/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 3 | cd $SCRIPT_DIR/.. 4 | 5 | release_dir=release 6 | mkdir $release_dir 7 | cp LICENSE.md $release_dir 8 | cp README.md $release_dir 9 | cp release_pack/CMakeLists.txt $release_dir 10 | cp build*/seal_lake_*/seal_lake.cmake $release_dir 11 | cp build*/seal_lake_*/CPM.cmake $release_dir 12 | cp -r include $release_dir 13 | 14 | -------------------------------------------------------------------------------- /external/seal_lake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | set(SEAL_LAKE_VERSION v0.4.0) 3 | set(FETCHCONTENT_QUIET FALSE) 4 | FetchContent_Declare(seal_lake_${SEAL_LAKE_VERSION} 5 | SOURCE_DIR seal_lake_${SEAL_LAKE_VERSION} 6 | GIT_REPOSITORY "https://github.com/kamchatka-volcano/seal_lake.git" 7 | GIT_TAG ${SEAL_LAKE_VERSION} 8 | ) 9 | FetchContent_MakeAvailable(seal_lake_${SEAL_LAKE_VERSION}) 10 | include(${seal_lake_${SEAL_LAKE_VERSION}_SOURCE_DIR}/seal_lake.cmake) -------------------------------------------------------------------------------- /include/figcone_tree/streamposition.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_TREE_STREAMPOSITION_H 2 | #define FIGCONE_TREE_STREAMPOSITION_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace figcone { 9 | 10 | struct StreamPosition { 11 | std::optional line; 12 | std::optional column; 13 | }; 14 | 15 | inline std::string streamPositionToString(const StreamPosition& pos) 16 | { 17 | auto ss = std::stringstream{}; 18 | if (pos.line) { 19 | ss << "[line:" << *pos.line; 20 | if (pos.column) 21 | ss << ", column:" << *pos.column; 22 | ss << "] "; 23 | } 24 | return ss.str(); 25 | } 26 | 27 | } //namespace figcone 28 | 29 | #endif //FIGCONE_TREE_STREAMPOSITION_H 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(figcone_tree VERSION 2.2.0 DESCRIPTION "C++17 interface library for creating configuration parsers compatible with figcone") 4 | include(external/seal_lake) 5 | 6 | SealLake_v040_Bundle( 7 | NAME figcone_tree_eel 8 | GIT_REPOSITORY https://github.com/kamchatka-volcano/eel.git 9 | GIT_TAG v0.3.0 10 | DIRECTORIES 11 | include/eel 12 | DESTINATION 13 | include/figcone_tree/detail/external 14 | TEXT_REPLACEMENTS 15 | "namespace eel" "namespace figcone::tree::eel" 16 | "EEL_" "FIGCONE_TREE_EEL_" 17 | ) 18 | 19 | SealLake_v040_HeaderOnlyLibrary( 20 | NAMESPACE figcone 21 | COMPILE_FEATURES cxx_std_17 22 | ) 23 | -------------------------------------------------------------------------------- /include/figcone_tree/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_TREE_ERRORS_H 2 | #define FIGCONE_TREE_ERRORS_H 3 | 4 | #include "streamposition.h" 5 | #include 6 | #include 7 | 8 | namespace figcone { 9 | 10 | class Error : public std::runtime_error { 11 | public: 12 | explicit Error(const std::string& errorMsg) 13 | : std::runtime_error(errorMsg) 14 | { 15 | } 16 | }; 17 | 18 | class ConfigError : public Error { 19 | public: 20 | using Error::Error; 21 | 22 | ConfigError(const std::string& errorMsg, const StreamPosition& errorPosition = {}) 23 | : Error(streamPositionToString(errorPosition) + errorMsg) 24 | { 25 | } 26 | }; 27 | 28 | class ValidationError : public Error { 29 | using Error::Error; 30 | }; 31 | 32 | } //namespace figcone 33 | 34 | #endif //FIGCONE_TREE_ERRORS_H 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: [ "v*" ] 6 | 7 | jobs: 8 | build: 9 | name: Publish release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Install ninja (Linux) 13 | run: sudo apt install ninja-build 14 | - uses: actions/checkout@v4 15 | - uses: rui314/setup-mold@v1 16 | - uses: hendrikmuhs/ccache-action@v1.2 17 | - name: Configure CMake to download dependencies 18 | run: cmake -B build --preset="clang-release" 19 | - name: Create release package 20 | run: release_pack/release.sh 21 | - name: Archive release artifacts 22 | run: | 23 | mv release figcone_tree-${{ github.ref_name }} 24 | zip -r figcone_tree-${{ github.ref_name }}.zip figcone_tree-${{ github.ref_name }} 25 | - name: Upload release 26 | uses: softprops/action-gh-release@v1 27 | with: 28 | files: | 29 | figcone_tree-${{ github.ref_name }}.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **figcone_tree** - is a C++17 header-only library providing an interface for creating configuration parsers compatible with [`figcone`](https://github.com/kamchatka-volcano/figcone) library. 2 | 3 | 4 | ## Installation 5 | Download and link the library from your project's CMakeLists.txt: 6 | ``` 7 | cmake_minimum_required(VERSION 3.18) 8 | 9 | include(FetchContent) 10 | FetchContent_Declare(figcone_tree 11 | GIT_REPOSITORY "https://github.com/kamchatka-volcano/figcone_tree.git" 12 | GIT_TAG "origin/master" 13 | ) 14 | #uncomment if you need to install figcone_tree with your target 15 | #set(INSTALL_FIGCONE_TREE ON) 16 | FetchContent_MakeAvailable(figcone_tree) 17 | 18 | add_executable(${PROJECT_NAME}) 19 | target_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone_tree) 20 | ``` 21 | Prefer using the release ZIP archive with FetchContent, as it is fully self-contained and avoids spending additional time downloading the library dependencies during the CMake configuration step. 22 | 23 | For the system-wide installation use these commands: 24 | ``` 25 | git clone https://github.com/kamchatka-volcano/figcone_tree.git 26 | cd figcone_tree 27 | cmake -S . -B build 28 | cmake --build build 29 | cmake --install build 30 | ``` 31 | 32 | Afterwards, you can use find_package() command to make the installed library available inside your project: 33 | ``` 34 | find_package(figcone_tree 0.9.0 REQUIRED) 35 | target_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone_tree) 36 | ``` 37 | 38 | ## License 39 | **figcone_tree** is licensed under the [MS-PL license](/LICENSE.md) 40 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AllowAllArgumentsOnNextLine: false 5 | AllowAllConstructorInitializersOnNextLine: false 6 | AllowAllParametersOfDeclarationOnNextLine: false 7 | AllowShortFunctionsOnASingleLine: Empty 8 | AllowShortLambdasOnASingleLine: Empty 9 | AllowShortEnumsOnASingleLine: false 10 | AllowShortIfStatementsOnASingleLine: Never 11 | AllowShortLoopsOnASingleLine: false 12 | AllowShortBlocksOnASingleLine: Empty 13 | AlwaysBreakTemplateDeclarations: Yes 14 | BinPackArguments: false 15 | BinPackParameters: false 16 | BraceWrapping: 17 | AfterFunction: true 18 | BeforeElse: true 19 | BeforeLambdaBody: true 20 | BeforeWhile: true 21 | BeforeCatch: true 22 | BreakBeforeBraces: Custom 23 | BreakBeforeBinaryOperators: None 24 | BreakInheritanceList: AfterComma 25 | ColumnLimit: 120 26 | ContinuationIndentWidth: 8 27 | Cpp11BracedListStyle: true 28 | NamespaceIndentation: None 29 | PenaltyBreakBeforeFirstCallParameter: 0 30 | PenaltyReturnTypeOnItsOwnLine: 1000 31 | PenaltyBreakAssignment: 10 32 | SpaceBeforeCpp11BracedList: false 33 | SpaceInEmptyBlock: false 34 | SpaceInEmptyParentheses: false 35 | SpaceAfterTemplateKeyword: false 36 | SpacesInLineCommentPrefix: 37 | Minimum: 0 38 | Maximum: -1 39 | FixNamespaceComments: true 40 | UseCRLF: false 41 | IncludeCategories: 42 | # Headers in <> without extension. 43 | - Regex: '<[[:alnum:]\-_]+>' 44 | Priority: 6 45 | # Headers in <> from specific external libraries. 46 | - Regex: '<(gtest|gmock|boost|gsl)\/' 47 | Priority: 5 48 | # Headers in <> with subdirectory. 49 | - Regex: '<[[:alnum:]\-_]+\/' 50 | Priority: 4 51 | # Headers in <> with extension. 52 | - Regex: '<[[:alnum:].\-_]+>' 53 | Priority: 3 54 | # Headers in "" with subdirectory. 55 | - Regex: '"[[:alnum:]\-_]+\/' 56 | Priority: 2 57 | # Headers in "" with extension. 58 | - Regex: '"[[:alnum:].\-_]+"' 59 | Priority: 1 60 | ... 61 | -------------------------------------------------------------------------------- /.github/workflows/version_check.yml: -------------------------------------------------------------------------------- 1 | name: check version consistency 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "master", "dev" ] 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | branches: [ "master", "dev" ] 11 | paths-ignore: 12 | - '**.md' 13 | 14 | jobs: 15 | version-check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Check version consistency 22 | run: | 23 | PROJECT_VERSION=$(grep -Po 'project\([^)]*VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) 24 | RELEASE_VERSION=$(grep -Po 'project\([^)]*VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' release_pack/CMakeLists.txt) 25 | 26 | if [ "$PROJECT_VERSION" != "$RELEASE_VERSION" ]; then 27 | echo "Error: Version mismatch between CMakeLists.txt ($PROJECT_VERSION) and release_pack/CMakeLists.txt ($RELEASE_VERSION)" 28 | exit 1 29 | fi 30 | - name: Check GIT_TAG format 31 | run: | 32 | FILES=$(find . -name CMakeLists.txt) 33 | BAD_TAGS=0 34 | 35 | echo "Checking GIT_TAG semantic version format..." 36 | 37 | for FILE in $FILES; do 38 | TAGS=$(grep -Po 'GIT_TAG\s+["'\'']?\K[^"'\''\s)]+' "$FILE" || true) 39 | for TAG in $TAGS; do 40 | if ! [[ "$TAG" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([-._][A-Za-z0-9]+)*$ ]]; then 41 | echo "Error: [file=$FILE] GIT_TAG '$TAG' does not start with a valid semantic version (e.g., 1.2.3, v1.2.3)" 42 | BAD_TAGS=1 43 | fi 44 | done 45 | done 46 | 47 | if [ "$BAD_TAGS" -ne 0 ]; then 48 | echo "Error: One or more GIT_TAG values don't start with a valid semantic version." 49 | exit 1 50 | else 51 | echo "All GIT_TAG values start with valid semantic versions." 52 | fi -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | Copyright 2022 Gorelyy PA 3 | 4 | This license governs use of the accompanying software. If you use the software, 5 | you accept this license. If you do not accept the license, do not use the 6 | software. 7 | 8 | 1. Definitions 9 | 10 | The terms "reproduce," "reproduction," "derivative works," and "distribution" 11 | have the same meaning here as under U.S. copyright law. 12 | 13 | A "contribution" is the original software, or any additions or changes to the 14 | software. 15 | 16 | A "contributor" is any person that distributes its contribution under this 17 | license. 18 | 19 | "Licensed patents" are a contributor's patent claims that read directly on its 20 | contribution. 21 | 22 | 2. Grant of Rights 23 | 24 | (A) Copyright Grant- Subject to the terms of this license, including the 25 | license conditions and limitations in section 3, each contributor grants you a 26 | non-exclusive, worldwide, royalty-free copyright license to reproduce its 27 | contribution, prepare derivative works of its contribution, and distribute its 28 | contribution or any derivative works that you create. 29 | 30 | (B) Patent Grant- Subject to the terms of this license, including the license 31 | conditions and limitations in section 3, each contributor grants you a 32 | non-exclusive, worldwide, royalty-free license under its licensed patents to 33 | make, have made, use, sell, offer for sale, import, and/or otherwise dispose of 34 | its contribution in the software or derivative works of the contribution in the 35 | software. 36 | 37 | 3. Conditions and Limitations 38 | 39 | (A) No Trademark License- This license does not grant you rights to use any 40 | contributors' name, logo, or trademarks. 41 | 42 | (B) If you bring a patent claim against any contributor over patents that you 43 | claim are infringed by the software, your patent license from such contributor 44 | to the software ends automatically. 45 | 46 | (C) If you distribute any portion of the software, you must retain all 47 | copyright, patent, trademark, and attribution notices that are present in the 48 | software. 49 | 50 | (D) If you distribute any portion of the software in source code form, you may 51 | do so only under this license by including a complete copy of this license with 52 | your distribution. If you distribute any portion of the software in compiled or 53 | object code form, you may only do so under a license that complies with this 54 | license. 55 | 56 | (E) The software is licensed "as-is." You bear the risk of using it. The 57 | contributors give no express warranties, guarantees or conditions. You may have 58 | additional consumer rights under your local laws which this license cannot 59 | change. To the extent permitted under your local laws, the contributors exclude 60 | the implied warranties of merchantability, fitness for a particular purpose and 61 | non-infringement. 62 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "configurePresets": [ 4 | { 5 | "name": "base-linux", 6 | "hidden": true, 7 | "displayName": "linux base preset", 8 | "generator": "Ninja", 9 | "binaryDir": "build-${presetName}", 10 | "cacheVariables": { 11 | "CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=mold", 12 | "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", 13 | "CPM_SOURCE_CACHE": "cpm_cache" 14 | } 15 | }, 16 | { 17 | "name": "clang-base", 18 | "hidden": true, 19 | "displayName": "clang base preset", 20 | "inherits": "base-linux", 21 | "cacheVariables": { 22 | "CMAKE_CXX_COMPILER": "clang++", 23 | "CMAKE_C_COMPILER": "clang", 24 | "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror -Wcast-align -Wnon-virtual-dtor -Woverloaded-virtual -Wunused" 25 | } 26 | }, 27 | { 28 | "name": "clang-debug", 29 | "displayName": "clang (Debug)", 30 | "inherits": "clang-base", 31 | "cacheVariables": { 32 | "CMAKE_BUILD_TYPE": "Debug" 33 | } 34 | }, 35 | { 36 | "name": "clang-release", 37 | "displayName": "clang (Release)", 38 | "inherits": "clang-base", 39 | "cacheVariables": { 40 | "CMAKE_BUILD_TYPE": "Release" 41 | } 42 | }, 43 | { 44 | "name": "gcc-base", 45 | "hidden": true, 46 | "displayName": "gcc base preset", 47 | "inherits": "base-linux", 48 | "cacheVariables": { 49 | "CMAKE_CXX_COMPILER": "g++", 50 | "CMAKE_C_COMPILER": "gcc", 51 | "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Werror -Wcast-align -Wnon-virtual-dtor -Woverloaded-virtual -Wunused" 52 | } 53 | }, 54 | { 55 | "name": "gcc-debug", 56 | "displayName": "gcc (Debug)", 57 | "inherits": "gcc-base", 58 | "cacheVariables": { 59 | "CMAKE_BUILD_TYPE": "Debug" 60 | } 61 | }, 62 | { 63 | "name": "gcc-release", 64 | "displayName": "gcc (Release)", 65 | "inherits": "gcc-base", 66 | "cacheVariables": { 67 | "CMAKE_BUILD_TYPE": "Release" 68 | } 69 | }, 70 | { 71 | "name": "base-windows", 72 | "displayName": "windows base preset", 73 | "hidden": true, 74 | "generator": "Ninja", 75 | "binaryDir": "build-${presetName}", 76 | "architecture": { 77 | "value": "x64", 78 | "strategy": "external" 79 | }, 80 | "cacheVariables": { 81 | "CPM_SOURCE_CACHE": "cpm_cache", 82 | "CMAKE_CXX_COMPILER_LAUNCHER": "ccache" 83 | }, 84 | "vendor": { 85 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 86 | "hostOS": [ 87 | "Windows" 88 | ] 89 | }, 90 | "jetbrains.com/clion": { 91 | "toolchain": "Visual Studio" 92 | } 93 | } 94 | }, 95 | { 96 | "name": "msvc-base", 97 | "hidden": true, 98 | "displayName": "msvc base preset", 99 | "inherits": "base-windows", 100 | "cacheVariables": { 101 | "CMAKE_CXX_COMPILER": "cl.exe", 102 | "CMAKE_C_COMPILER": "cl.exe", 103 | "CMAKE_CXX_FLAGS": "/EHsc /W4 /WX" 104 | } 105 | }, 106 | { 107 | "name": "msvc-debug", 108 | "displayName": "msvc (Debug)", 109 | "inherits": "msvc-base", 110 | "cacheVariables": { 111 | "CMAKE_BUILD_TYPE": "Debug" 112 | } 113 | }, 114 | { 115 | "name": "msvc-release", 116 | "displayName": "msvc (Release)", 117 | "inherits": "msvc-base", 118 | "cacheVariables": { 119 | "CMAKE_BUILD_TYPE": "Release" 120 | } 121 | } 122 | ] 123 | } -------------------------------------------------------------------------------- /include/figcone_tree/tree.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_TREE_TREE_H 2 | #define FIGCONE_TREE_TREE_H 3 | 4 | #include "errors.h" 5 | #include "streamposition.h" 6 | #include "detail/external/eel/functional.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace figcone { 17 | 18 | namespace detail { 19 | 20 | template 21 | std::vector keys(const std::map& map) 22 | { 23 | auto result = std::vector{}; 24 | result.reserve(map.size()); 25 | std::transform( 26 | map.begin(), 27 | map.end(), 28 | std::back_inserter(result), 29 | [](const auto& pair) 30 | { 31 | return pair.first; 32 | }); 33 | return result; 34 | } 35 | 36 | } //namespace detail 37 | 38 | class TreeParam { 39 | struct Item { 40 | std::string value; 41 | }; 42 | struct List { 43 | std::vector valueList; 44 | }; 45 | 46 | public: 47 | explicit TreeParam(std::string value, const StreamPosition& position = {}) 48 | : position_(position) 49 | , data_(Item{std::move(value)}) 50 | { 51 | } 52 | 53 | explicit TreeParam(std::vector valueList, const StreamPosition& position = {}) 54 | : position_(position) 55 | , data_(List{std::move(valueList)}) 56 | { 57 | } 58 | 59 | bool isItem() const 60 | { 61 | return std::holds_alternative(data_); 62 | } 63 | 64 | bool isList() const 65 | { 66 | return std::holds_alternative(data_); 67 | } 68 | 69 | const std::string& value() const 70 | { 71 | if (!isItem()) 72 | throw ConfigError{"Bad parameter access - trying to get a list as a single item", position_}; 73 | 74 | return std::get(data_).value; 75 | } 76 | 77 | const std::vector& valueList() const 78 | { 79 | if (!isList()) 80 | throw ConfigError{"Bad parameter access - trying to get a single item as a list", position_}; 81 | 82 | return std::get(data_).valueList; 83 | } 84 | 85 | StreamPosition position() const 86 | { 87 | return position_; 88 | } 89 | 90 | private: 91 | StreamPosition position_; 92 | std::variant data_; 93 | }; 94 | 95 | class TreeNode { 96 | enum class Type { 97 | Item, 98 | List, 99 | Any 100 | }; 101 | 102 | explicit TreeNode(Type type, std::string name = {}, const StreamPosition& position = {}) 103 | : data_{List()} 104 | , type_{type} 105 | , name_{std::move(name)} 106 | , position_{position} 107 | { 108 | switch (type_) { 109 | case Type::Any: 110 | case Type::List: 111 | data_.emplace(); 112 | break; 113 | case Type::Item: 114 | data_.emplace(std::nullopt); 115 | break; 116 | } 117 | } 118 | 119 | public: 120 | class List { 121 | public: 122 | int size() const 123 | { 124 | return static_cast(nodeList_.size()); 125 | } 126 | 127 | const TreeNode& at(int index) const 128 | { 129 | return *nodeList_.at(static_cast(index)); 130 | } 131 | 132 | TreeNode& emplaceBack(std::string_view name, const StreamPosition& pos = {}) 133 | { 134 | auto node = std::unique_ptr{new TreeNode(TreeNode::Type::Item, std::string{name}, pos)}; 135 | return *nodeList_.emplace_back(std::move(node)); 136 | } 137 | 138 | TreeNode& emplaceBack(const StreamPosition& pos = {}) 139 | { 140 | auto node = std::unique_ptr{new TreeNode(TreeNode::Type::Item, {}, pos)}; 141 | return *nodeList_.emplace_back(std::move(node)); 142 | } 143 | 144 | TreeNode& emplaceBackAny(std::string_view name, const StreamPosition& pos = {}) 145 | { 146 | auto node = std::unique_ptr{new TreeNode(TreeNode::Type::Any, std::string{name}, pos)}; 147 | return *nodeList_.emplace_back(std::move(node)); 148 | } 149 | 150 | TreeNode& emplaceBackAny(const StreamPosition& pos = {}) 151 | { 152 | auto node = std::unique_ptr{new TreeNode(TreeNode::Type::Any, {}, pos)}; 153 | return *nodeList_.emplace_back(std::move(node)); 154 | } 155 | 156 | private: 157 | std::vector> nodeList_; 158 | friend class TreeNode; 159 | }; 160 | 161 | class Item { 162 | public: 163 | Item(std::optional>>> nodeList = std::nullopt) 164 | : nodeList_{nodeList} 165 | { 166 | } 167 | 168 | int paramsCount(bool checkIfInvokedThroughAdapterItem = true) const 169 | { 170 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 171 | return asAnyListItemAdapter()->paramsCount(false); 172 | 173 | return static_cast(params_.size()); 174 | } 175 | 176 | int nodesCount(bool checkIfInvokedThroughAdapterItem = true) const 177 | { 178 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 179 | return asAnyListItemAdapter()->nodesCount(false); 180 | 181 | return static_cast(nodes_.size()); 182 | } 183 | 184 | bool hasParam(const std::string& name, bool checkIfInvokedThroughAdapterItem = true) const 185 | { 186 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 187 | return asAnyListItemAdapter()->hasParam(name, false); 188 | 189 | return params_.find(name) != params_.end(); 190 | } 191 | 192 | bool hasNode(const std::string& name, bool checkIfInvokedThroughAdapterItem = true) const 193 | { 194 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) { 195 | if (nodeList_.value().get().at(0)->name_ == name) 196 | return true; 197 | return asAnyListItemAdapter()->hasNode(name, false); 198 | } 199 | 200 | return nodes_.find(name) != nodes_.end(); 201 | } 202 | 203 | 204 | const TreeParam& param(const std::string& name, bool checkIfInvokedThroughAdapterItem = true) const 205 | { 206 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 207 | return asAnyListItemAdapter()->param(name, false); 208 | 209 | return params_.at(name); 210 | } 211 | 212 | std::vector paramNames() const 213 | { 214 | if (isAnyListItemAdapter()) 215 | return detail::keys(asAnyListItemAdapter()->params_); 216 | 217 | return detail::keys(params_); 218 | } 219 | 220 | const TreeNode& node(const std::string& name, bool checkIfInvokedThroughAdapterItem = true) const 221 | { 222 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) { 223 | if (nodeList_.value().get().at(0)->name_ == name) 224 | return *nodeList_.value().get().at(0); 225 | 226 | return asAnyListItemAdapter()->node(name, false); 227 | } 228 | 229 | return nodes_.at(name); 230 | } 231 | 232 | std::vector nodeNames() const 233 | { 234 | if (isAnyListItemAdapter()) 235 | return detail::keys(asAnyListItemAdapter()->nodes_); 236 | 237 | return detail::keys(nodes_); 238 | } 239 | 240 | TreeNode& addNode( 241 | const std::string& name, 242 | const StreamPosition& pos = {}, 243 | bool checkIfInvokedThroughAdapterItem = true) 244 | { 245 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 246 | return asAnyListItemAdapter()->addNode(name, pos, false); 247 | 248 | auto node = TreeNode{Type::Item, name, pos}; 249 | auto [it, ok] = nodes_.emplace(name, std::move(node)); 250 | if (!ok) 251 | throw ConfigError{"Node '" + name + "' already exists", pos}; 252 | 253 | return it->second; 254 | } 255 | 256 | TreeNode& addNodeList( 257 | const std::string& name, 258 | const StreamPosition& pos = {}, 259 | bool checkIfInvokedThroughAdapterItem = true) 260 | { 261 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 262 | return asAnyListItemAdapter()->addNodeList(name, pos, false); 263 | 264 | auto node = TreeNode{Type::List, name, pos}; 265 | auto [it, ok] = nodes_.emplace(name, std::move(node)); 266 | if (!ok) 267 | throw ConfigError{"Node list '" + name + "' already exists", pos}; 268 | 269 | return it->second; 270 | } 271 | 272 | TreeNode& addAny( 273 | const std::string& name, 274 | const StreamPosition& pos = {}, 275 | bool checkIfInvokedThroughAdapterItem = true) 276 | { 277 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 278 | return asAnyListItemAdapter()->addAny(name, pos, false); 279 | 280 | auto node = TreeNode{Type::Any, name, pos}; 281 | auto [it, ok] = nodes_.emplace(name, std::move(node)); 282 | if (!ok) 283 | throw ConfigError{"Node '" + name + "' already exists", pos}; 284 | 285 | return it->second; 286 | } 287 | 288 | void addParam( 289 | const std::string& name, 290 | const std::string& value, 291 | const StreamPosition& pos = {}, 292 | bool checkIfInvokedThroughAdapterItem = true) 293 | { 294 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) { 295 | return asAnyListItemAdapter()->addParam(name, value, pos, false); 296 | } 297 | 298 | auto [_, ok] = params_.emplace(name, TreeParam{value, pos}); 299 | if (!ok) 300 | throw ConfigError{"Parameter '" + name + "' already exists", pos}; 301 | } 302 | 303 | void addParamList( 304 | const std::string& name, 305 | const std::vector& valueList, 306 | const StreamPosition& pos = {}, 307 | bool checkIfInvokedThroughAdapterItem = true) 308 | { 309 | if (checkIfInvokedThroughAdapterItem && isAnyListItemAdapter()) 310 | return asAnyListItemAdapter()->addParamList(name, valueList, pos, checkIfInvokedThroughAdapterItem); 311 | 312 | auto [_, ok] = params_.emplace(name, TreeParam{valueList, pos}); 313 | if (!ok) 314 | throw ConfigError{"Parameter list '" + name + "' already exists", pos}; 315 | } 316 | 317 | private: 318 | bool isAnyListItemAdapter() const 319 | { 320 | return nodeList_.has_value() && !nodeList_.value().get().empty(); 321 | } 322 | 323 | const Item* asAnyListItemAdapter() const 324 | { 325 | if (nodeList_.has_value() && !nodeList_.value().get().empty()) 326 | return &std::as_const(*nodeList_.value().get().at(0)).asItem(); 327 | return nullptr; 328 | } 329 | 330 | Item* asAnyListItemAdapter() 331 | { 332 | if (nodeList_.has_value() && !nodeList_.value().get().empty()) 333 | return &nodeList_.value().get().at(0)->asItem(); 334 | return nullptr; 335 | } 336 | 337 | private: 338 | std::map params_; 339 | std::map nodes_; 340 | std::optional>>> nodeList_; 341 | }; 342 | 343 | public: 344 | bool isItem() const 345 | { 346 | if (type_ == Type::Any) 347 | return std::get(data_).size() == 1; 348 | 349 | return std::holds_alternative(data_); 350 | } 351 | 352 | bool isList() const 353 | { 354 | if (type_ == Type::Any) 355 | return true; 356 | 357 | return std::holds_alternative(data_); 358 | } 359 | 360 | const Item& asItem() const 361 | { 362 | if (type_ == Type::Any) { 363 | if (asList().size() > 1) 364 | throw ConfigError{"Bad any node access - trying to get multiple items as a single item", position_}; 365 | return listAdapterItem_; 366 | } 367 | 368 | if (!isItem()) 369 | throw ConfigError{"Bad node access - trying to get a list as a single item", position_}; 370 | 371 | return std::get(data_); 372 | } 373 | 374 | const List& asList() const 375 | { 376 | if (!isList()) 377 | throw ConfigError{"Bad node access - trying to get a single item as a list", position_}; 378 | 379 | return std::get(data_); 380 | } 381 | 382 | Item& asItem() 383 | { 384 | if (type_ == Type::Any) { 385 | if (prevAccess != Type::Item) { 386 | prevAccess = Type::Item; 387 | auto& list = data_.emplace(); 388 | listAdapterItem_ = Item{list.nodeList_}; 389 | list.emplaceBack(name_); 390 | return listAdapterItem_; 391 | } 392 | if (std::get(data_).size() != 1) 393 | throw ConfigError{ 394 | "Bad any node access - trying to get zero or multiple items as a single item", 395 | position_}; 396 | 397 | prevAccess = Type::Item; 398 | return listAdapterItem_; 399 | } 400 | 401 | if (!isItem()) 402 | throw ConfigError{"Bad node access - trying to get a list as a single item", position_}; 403 | return std::get(data_); 404 | } 405 | 406 | List& asList() 407 | { 408 | if (type_ == Type::Any && prevAccess != Type::List) { 409 | prevAccess = Type::List; 410 | auto& list = data_.emplace(); 411 | listAdapterItem_ = Item{list.nodeList_}; 412 | return list; 413 | } 414 | 415 | if (!isList()) 416 | throw ConfigError{"Bad node access - trying to get a single item as a list", position_}; 417 | return std::get(data_); 418 | } 419 | 420 | const StreamPosition& position() const 421 | { 422 | return position_; 423 | } 424 | 425 | bool isRoot() const 426 | { 427 | return isRoot_; 428 | } 429 | 430 | private: 431 | std::variant data_; 432 | Type type_ = Type::Item; 433 | Type prevAccess = Type::Any; 434 | std::string name_; 435 | StreamPosition position_ = {1, 1}; 436 | Item listAdapterItem_; 437 | bool isRoot_ = false; 438 | 439 | friend std::unique_ptr makeTreeRoot(); 440 | friend std::unique_ptr makeTreeRootList(); 441 | }; 442 | 443 | inline std::unique_ptr makeTreeRoot() 444 | { 445 | auto root = std::unique_ptr{new TreeNode(TreeNode::Type::Item, "root", {1, 1})}; 446 | root->isRoot_ = true; 447 | return root; 448 | } 449 | 450 | inline std::unique_ptr makeTreeRootList() 451 | { 452 | auto root = std::unique_ptr{new TreeNode(TreeNode::Type::List, "root", {1, 1})}; 453 | root->isRoot_ = true; 454 | return root; 455 | } 456 | 457 | class Tree { 458 | public: 459 | Tree(std::unique_ptr root) 460 | : root_(std::move(root)) 461 | { 462 | } 463 | 464 | const TreeNode& root() const 465 | { 466 | return *root_; 467 | } 468 | 469 | private: 470 | std::unique_ptr root_; 471 | }; 472 | 473 | } //namespace figcone 474 | 475 | #endif //FIGCONE_TREE_TREE_H 476 | --------------------------------------------------------------------------------