├── src ├── paramparser.h ├── parser.cpp ├── configreadresult.h ├── utils.h ├── nodeparser.h ├── stream.h ├── utils.cpp ├── stream.cpp ├── paramparser.cpp └── nodeparser.cpp ├── external └── seal_lake ├── tests ├── CMakeLists.txt ├── assert_exception.h ├── test_paramparser.cpp ├── test_nodeparser.cpp ├── test_paramlistparser.cpp └── test_nodelistparser.cpp ├── release_pack ├── release.sh ├── figcone_shoal_deps │ └── CMakeLists.txt └── CMakeLists.txt ├── include └── figcone_shoal │ └── parser.h ├── CMakeLists.txt ├── .github └── workflows │ ├── release.yml │ ├── build_and_test.yml │ └── version_check.yml ├── README.md ├── .clang-format ├── LICENSE.md └── CMakePresets.json /src/paramparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace figcone::shoal::detail { 5 | class Stream; 6 | 7 | std::pair parseParam(Stream& stream); 8 | 9 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /src/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "nodeparser.h" 2 | #include "stream.h" 3 | #include 4 | 5 | namespace figcone::shoal { 6 | 7 | Tree Parser::parse(std::istream& stream) 8 | { 9 | auto inputStream = detail::Stream{stream}; 10 | auto rootNode = makeTreeRoot(); 11 | detail::parseNode(inputStream, *rootNode, ""); 12 | return Tree{std::move(rootNode)}; 13 | } 14 | 15 | } //namespace figcone::shoal 16 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /src/configreadresult.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace figcone::shoal::detail { 6 | 7 | struct ConfigReadResult { 8 | enum class NextAction { 9 | ContinueReading, 10 | ReturnToParentNode, 11 | ReturnToNodeByName, 12 | ReturnToRootNode 13 | } nextAction; 14 | std::string parentNodeName; 15 | StreamPosition returnToNodeStreamPosition; 16 | }; 17 | 18 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(test_figcone_shoal) 2 | 3 | set(SRC 4 | test_paramparser.cpp 5 | test_paramlistparser.cpp 6 | test_nodeparser.cpp 7 | test_nodelistparser.cpp 8 | ) 9 | 10 | if(FIGCONE_SHOAL_TEST_RELEASE) 11 | add_subdirectory(release) 12 | endif() 13 | 14 | SealLake_v040_GoogleTest( 15 | SOURCES ${SRC} 16 | COMPILE_FEATURES cxx_std_17 17 | INCLUDES ../src 18 | PROPERTIES 19 | CXX_EXTENSIONS OFF 20 | LIBRARIES figcone::figcone_shoal 21 | ) 22 | -------------------------------------------------------------------------------- /tests/assert_exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template 6 | void assert_exception( 7 | const std::function& throwingCode, 8 | std::function exceptionContentChecker) 9 | { 10 | try { 11 | throwingCode(); 12 | FAIL() << "exception wasn't thrown!"; 13 | } 14 | catch (const TException& e) { 15 | exceptionContentChecker(e); 16 | } 17 | catch (...) { 18 | FAIL() << "Unexpected exception was thrown"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace figcone::shoal::detail { 7 | class Stream; 8 | 9 | bool isBlank(const std::string& str); 10 | void skipLine(Stream& stream); 11 | void skipWhitespace(Stream& stream, bool withNewLine = true); 12 | std::string readUntil(Stream& stream, std::function stopPred); 13 | std::string readUntil(Stream& stream, const std::string& stopChars = {}); 14 | std::string readWord(Stream& stream, const std::string& stopChars = {}); 15 | std::optional readQuotedString(Stream& stream); 16 | 17 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /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 build*/seal_lake_*/seal_lake.cmake release_pack/figcone_shoal_deps 8 | cp build*/seal_lake_*/CPM.cmake release_pack/figcone_shoal_deps 9 | cd release_pack/figcone_shoal_deps 10 | cmake -S . 11 | cd ../../ 12 | cp -r release_pack/figcone_shoal_deps/deps $release_dir 13 | 14 | cp release_pack/CMakeLists.txt $release_dir 15 | cp build*/seal_lake_*/seal_lake.cmake $release_dir 16 | cp build*/seal_lake_*/CPM.cmake $release_dir 17 | cp -r include $release_dir 18 | cp -r src $release_dir 19 | cp LICENSE.md $release_dir 20 | cp README.md $release_dir 21 | -------------------------------------------------------------------------------- /release_pack/figcone_shoal_deps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(figcone_shoal_deps) 4 | include(seal_lake.cmake) 5 | 6 | SealLake_v040_Bundle( 7 | NAME figcone_shoal_eel 8 | URL https://github.com/kamchatka-volcano/eel/releases/download/v0.3.0/eel-v0.3.0.zip 9 | SKIP_LOAD 10 | DESTINATION deps/figcone_shoal_eel 11 | TEXT_REPLACEMENTS 12 | "namespace eel" "namespace figcone::shoal::eel" 13 | "EEL_" "FIGCONE_SHOAL_EEL_" 14 | ) 15 | 16 | SealLake_v040_Bundle( 17 | NAME figcone_tree 18 | URL https://github.com/kamchatka-volcano/figcone_tree/releases/download/v2.2.0/figcone_tree-v2.2.0.zip 19 | SKIP_LOAD 20 | DESTINATION deps/figcone_tree 21 | ) -------------------------------------------------------------------------------- /include/figcone_shoal/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef FIGCONE_SHOAL_PARSER_H 2 | #define FIGCONE_SHOAL_PARSER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace figcone { 9 | template<> 10 | struct StringConverter { 11 | static std::optional fromString(const std::string& data) 12 | { 13 | if (data == "true" || data == "1") 14 | return true; 15 | if (data == "false" || data == "0") 16 | return false; 17 | return std::nullopt; 18 | } 19 | }; 20 | } //namespace figcone 21 | 22 | namespace figcone::shoal { 23 | 24 | class Parser : public IParser { 25 | public: 26 | Tree parse(std::istream& stream) override; 27 | }; 28 | 29 | } //namespace figcone::shoal 30 | 31 | #endif //FIGCONE_SHOAL_PARSER_H -------------------------------------------------------------------------------- /src/nodeparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "configreadresult.h" 3 | #include 4 | 5 | namespace figcone { 6 | class TreeNode; 7 | } 8 | 9 | namespace figcone::shoal::detail { 10 | class Stream; 11 | 12 | std::string readNodeName(Stream& stream); 13 | ConfigReadResult readEndToken(Stream& stream); 14 | ConfigReadResult checkReadResult( 15 | const ConfigReadResult& readResult, 16 | const std::string& newNodeName, 17 | const figcone::TreeNode& parentNode); 18 | std::optional parseListElementNodeSection( 19 | Stream& stream, 20 | figcone::TreeNode& parent, 21 | const std::string& parentName); 22 | 23 | std::optional parseNodeSection(Stream& stream, figcone::TreeNode& parent); 24 | ConfigReadResult parseNode(Stream& stream, figcone::TreeNode& node, const std::string& nodeName); 25 | 26 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /release_pack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(figcone_shoal VERSION 1.2.0 DESCRIPTION "shoal parser for figcone library") 4 | include(seal_lake.cmake) 5 | 6 | SealLake_v040_IsInstallEnabled(INSTALL_FIGCONE_TREE) 7 | find_package(figcone_tree 2.2.0 QUIET) 8 | if (NOT TARGET figcone::figcone_tree) 9 | add_subdirectory(deps/figcone_tree) 10 | endif() 11 | 12 | SealLake_v040_LoadDirectory(deps/figcone_shoal_eel) 13 | 14 | SealLake_v040_ObjectLibrary( 15 | NAMESPACE figcone 16 | COMPILE_FEATURES cxx_std_11 17 | SOURCES 18 | src/parser.cpp 19 | src/nodeparser.cpp 20 | src/paramparser.cpp 21 | src/stream.cpp 22 | src/utils.cpp 23 | INTERFACE_LIBRARIES 24 | figcone::figcone_tree 25 | LIBRARIES 26 | figcone_shoal_eel::figcone_shoal_eel 27 | DEPENDENCIES 28 | figcone_tree 2.2.0 29 | ) 30 | -------------------------------------------------------------------------------- /src/stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace figcone::shoal::detail { 7 | 8 | class Stream { 9 | public: 10 | explicit Stream(std::istream& stream, const StreamPosition& startPosition = StreamPosition{1, 1}); 11 | Stream(Stream&&) = default; 12 | Stream(const Stream&) = delete; 13 | Stream& operator=(const Stream&) = delete; 14 | 15 | void skip(int size); 16 | void skipLineSeparator(); 17 | void skipComments(bool state); 18 | std::string read(int size = 1); 19 | std::string peek(int size = 1); 20 | bool atEnd(); 21 | StreamPosition position() const; 22 | 23 | private: 24 | void skipLine(); 25 | 26 | private: 27 | std::istream& stream_; 28 | StreamPosition position_ = {0, 0}; 29 | StreamPosition startPosition_ = {0, 0}; 30 | bool skipComments_ = true; 31 | }; 32 | 33 | } //namespace figcone::shoal::detail 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(figcone_shoal VERSION 1.2.0 DESCRIPTION "shoal parser for figcone library") 4 | include(external/seal_lake) 5 | 6 | SealLake_v040_IsInstallEnabled(INSTALL_FIGCONE_TREE) 7 | SealLake_v040_Import( 8 | figcone_tree 2.2.0 9 | GIT_REPOSITORY https://github.com/kamchatka-volcano/figcone_tree.git 10 | GIT_TAG v2.2.0 11 | ) 12 | 13 | SealLake_v040_Bundle( 14 | NAME figcone_shoal_eel 15 | GIT_REPOSITORY https://github.com/kamchatka-volcano/eel.git 16 | GIT_TAG v0.3.0 17 | TEXT_REPLACEMENTS 18 | "namespace eel" "namespace figcone::shoal::eel" 19 | EEL_ FIGCONE_SHOAL_EEL_ 20 | ) 21 | 22 | SealLake_v040_ObjectLibrary( 23 | NAMESPACE figcone 24 | COMPILE_FEATURES cxx_std_11 25 | SOURCES 26 | src/parser.cpp 27 | src/nodeparser.cpp 28 | src/paramparser.cpp 29 | src/stream.cpp 30 | src/utils.cpp 31 | LIBRARIES figcone_shoal_eel::figcone_shoal_eel 32 | INTERFACE_LIBRARIES figcone::figcone_tree 33 | DEPENDENCIES 34 | figcone_tree 2.2.0 35 | ) 36 | 37 | SealLake_v040_OptionalSubProjects(tests) -------------------------------------------------------------------------------- /.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: Build unit tests with release package 22 | working-directory: tests 23 | run: | 24 | mv ../release . 25 | cp release/seal_lake.cmake . 26 | cp release/CPM.cmake . 27 | cmake -B build -DCMAKE_BUILD_TYPE=Release -DFIGCONE_SHOAL_TEST_RELEASE=ON 28 | cmake --build build 29 | - name: Run unit tests 30 | working-directory: tests/build 31 | run: ./test_figcone_shoal 32 | - name: Archive release artifacts 33 | working-directory: tests 34 | run: | 35 | mv release figcone_shoal-${{ github.ref_name }} 36 | zip -r figcone_shoal-${{ github.ref_name }}.zip figcone_shoal-${{ github.ref_name }} 37 | - name: Upload release 38 | uses: softprops/action-gh-release@v1 39 | with: 40 | files: | 41 | tests/figcone_shoal-${{ github.ref_name }}.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build & test (clang, gcc, MSVC)](https://github.com/kamchatka-volcano/figcone_shoal/actions/workflows/build_and_test.yml/badge.svg?branch=master)](https://github.com/kamchatka-volcano/figcone_shoal/actions/workflows/build_and_test.yml) 2 | 3 | **figcone_shoal** - is a [`shoal`](https://shoal.eelnet.org/) configuration parser for [`figcone`](https://github.com/kamchatka-volcano/figcone) library. 4 | 5 | 6 | ## Installation 7 | Download and link the library from your project's CMakeLists.txt: 8 | ``` 9 | cmake_minimum_required(VERSION 3.14) 10 | 11 | include(FetchContent) 12 | FetchContent_Declare(figcone_shoal 13 | GIT_REPOSITORY "https://github.com/kamchatka-volcano/figcone_shoal.git" 14 | GIT_TAG "origin/master" 15 | ) 16 | #uncomment if you need to install figcone_shoal with your target 17 | #set(INSTALL_FIGCONE_SHOAL ON) 18 | FetchContent_MakeAvailable(figcone_shoal) 19 | 20 | add_executable(${PROJECT_NAME}) 21 | target_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone_shoal) 22 | ``` 23 | 24 | For the system-wide installation use these commands: 25 | ``` 26 | git clone https://github.com/kamchatka-volcano/figcone_shoal.git 27 | cd figcone_shoal 28 | cmake -S . -B build 29 | cmake --build build 30 | cmake --install build 31 | ``` 32 | 33 | Afterwards, you can use find_package() command to make the installed library available inside your project: 34 | ``` 35 | find_package(figcone_shoal 0.1.0 REQUIRED) 36 | target_link_libraries(${PROJECT_NAME} PRIVATE figcone::figcone_shoal) 37 | ``` 38 | 39 | ## Running tests 40 | ``` 41 | cd figcone_shoal 42 | cmake -S . -B build -DENABLE_TESTS=ON 43 | cmake --build build 44 | cd build/tests && ctest 45 | ``` 46 | 47 | ## License 48 | **figcone_shoal** is licensed under the [MS-PL license](/LICENSE.md) 49 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: build & test (clang, gcc, MSVC) 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 | build: 16 | name: ${{ matrix.config.name }} 17 | runs-on: ${{ matrix.config.os }} 18 | env: 19 | CC: ${{ matrix.config.cc }} 20 | CXX: ${{ matrix.config.cxx }} 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | config: 26 | - { 27 | name: "Ubuntu Latest gcc", 28 | os: ubuntu-latest, 29 | cmake-preset: gcc-release 30 | } 31 | - { 32 | name: "Ubuntu Latest clang", 33 | os: ubuntu-latest, 34 | cmake-preset: clang-release 35 | } 36 | - { 37 | name: "Windows Latest MSVC", 38 | os: windows-latest, 39 | cmake-preset: msvc-release 40 | } 41 | 42 | steps: 43 | - name: Install ninja (Windows) 44 | if: matrix.config.os == 'windows-latest' 45 | run: choco install ninja 46 | - name: Install ninja (Linux) 47 | if: matrix.config.os == 'ubuntu-latest' 48 | run: sudo apt install ninja-build 49 | - uses: actions/checkout@v4 50 | 51 | - uses: rui314/setup-mold@v1 52 | - uses: hendrikmuhs/ccache-action@v1.2 53 | - uses: ilammy/msvc-dev-cmd@v1 54 | 55 | - name: Configure CMake 56 | run: cmake -B ${{github.workspace}}/build -DENABLE_TESTS=ON -DCMAKE_CXX_FLAGS="${{ matrix.config.flags }}" 57 | 58 | - name: Build 59 | run: cmake --build ${{github.workspace}}/build 60 | 61 | - name: Test 62 | working-directory: ${{github.workspace}}/build 63 | run: ctest 64 | 65 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include "stream.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace figcone::shoal::detail { 9 | 10 | bool isBlank(const std::string& str) 11 | { 12 | return std::all_of(str.begin(), str.end(), eel::isspace); 13 | } 14 | 15 | void skipLine(Stream& stream) 16 | { 17 | while (!stream.atEnd()) 18 | if (stream.read().front() == '\n') 19 | return; 20 | } 21 | 22 | void skipWhitespace(Stream& stream, bool withNewLine) 23 | { 24 | while (!stream.atEnd()) { 25 | const auto nextChar = stream.peek().front(); 26 | if (!withNewLine && nextChar == '\n') 27 | return; 28 | 29 | if (eel::isspace(nextChar)) 30 | stream.skip(1); 31 | else 32 | return; 33 | } 34 | } 35 | 36 | std::string readUntil(Stream& stream, std::function stopPred) 37 | { 38 | auto result = std::string{}; 39 | while (!stream.atEnd()) { 40 | if (stopPred(stream.peek().front())) 41 | return result; 42 | result += stream.read(); 43 | } 44 | return result; 45 | } 46 | 47 | std::string readUntil(Stream& stream, const std::string& stopChars) 48 | { 49 | return readUntil( 50 | stream, 51 | [&stopChars](char ch) 52 | { 53 | return stopChars.find(ch) != std::string::npos; 54 | }); 55 | } 56 | 57 | std::string readWord(Stream& stream, const std::string& stopChars) 58 | { 59 | return readUntil( 60 | stream, 61 | [&stopChars](char ch) 62 | { 63 | return eel::isspace(ch) || stopChars.find(ch) != std::string::npos; 64 | }); 65 | } 66 | 67 | std::optional readQuotedString(Stream& stream) 68 | { 69 | if (stream.atEnd()) 70 | return {}; 71 | 72 | const auto quotationMark = stream.peek().front(); 73 | if (quotationMark != '\'' && quotationMark != '"' && quotationMark != '`') 74 | return {}; 75 | 76 | stream.skipComments(false); 77 | const auto restoreSkipOnExit = eel::final_action{ 78 | [&stream] 79 | { 80 | stream.skipComments(true); 81 | }}; 82 | const auto pos = stream.position(); 83 | stream.skip(1); 84 | 85 | if (stream.peek() == "\n") 86 | stream.skipLineSeparator(); 87 | 88 | auto result = std::string{}; 89 | while (!stream.atEnd()) { 90 | const auto ch = stream.read().front(); 91 | if (ch == quotationMark) 92 | return result; 93 | result += ch; 94 | } 95 | throw ConfigError{"String isn't closed", pos}; 96 | } 97 | 98 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "stream.h" 2 | 3 | namespace figcone::shoal::detail { 4 | 5 | Stream::Stream(std::istream& stream, const StreamPosition& startPosition) 6 | : stream_(stream) 7 | , startPosition_(startPosition) 8 | { 9 | } 10 | 11 | void Stream::skip(int size) 12 | { 13 | read(size); 14 | } 15 | 16 | void Stream::skipLineSeparator() 17 | { 18 | auto ch = char{}; 19 | auto pos = stream_.tellg(); 20 | if (!stream_.get(ch)) 21 | return; 22 | if (ch == '\n') 23 | return; 24 | else if (ch == '\r') { 25 | pos = stream_.tellg(); 26 | if (!stream_.get(ch)) 27 | return; 28 | if (ch != '\n') 29 | stream_.seekg(pos); 30 | } 31 | else 32 | stream_.seekg(pos); 33 | } 34 | 35 | void Stream::skipComments(bool state) 36 | { 37 | skipComments_ = state; 38 | } 39 | 40 | std::string Stream::read(int size) 41 | { 42 | auto result = std::string{}; 43 | auto ch = char{}; 44 | for (auto i = 0; i < size; ++i) { 45 | if (!stream_.get(ch)) 46 | return {}; 47 | if (skipComments_ && ch == ';') { 48 | skipLine(); 49 | continue; 50 | } 51 | if (ch == '\r' && peek() == "\n") { 52 | i--; 53 | continue; 54 | } 55 | else if (ch == '\r' || ch == '\n') { 56 | (*position_.line)++; 57 | (*position_.column) = 0; 58 | ch = '\n'; 59 | } 60 | else if (ch == '\t') 61 | (*position_.column) += 4; 62 | else 63 | (*position_.column)++; 64 | result.push_back(ch); 65 | } 66 | return result; 67 | } 68 | 69 | std::string Stream::peek(int size) 70 | { 71 | auto result = std::string{}; 72 | auto ch = char{}; 73 | auto pos = stream_.tellg(); 74 | for (auto i = 0; i < size; ++i) { 75 | if (!stream_.get(ch)) { 76 | stream_.clear(); 77 | result.clear(); 78 | break; 79 | } 80 | if (skipComments_ && ch == ';') { 81 | skipLine(); 82 | i--; 83 | } 84 | else if (ch == '\r') { 85 | auto crPos = stream_.tellg(); 86 | if (stream_.get(ch)) { 87 | if (ch != '\n') 88 | stream_.seekg(crPos); 89 | } 90 | else 91 | stream_.clear(); 92 | 93 | result.push_back('\n'); 94 | } 95 | else 96 | result.push_back(ch); 97 | } 98 | stream_.seekg(pos); 99 | return result; 100 | } 101 | 102 | bool Stream::atEnd() 103 | { 104 | return peek().empty(); 105 | } 106 | 107 | StreamPosition Stream::position() const 108 | { 109 | return {*startPosition_.line + *position_.line, *startPosition_.column + *position_.column}; 110 | } 111 | 112 | void Stream::skipLine() 113 | { 114 | auto ch = char{}; 115 | auto prevPos = stream_.tellg(); 116 | while (stream_.get(ch)) { 117 | if (ch == '\r' || ch == '\n') { 118 | stream_.seekg(prevPos); 119 | break; 120 | } 121 | prevPos = stream_.tellg(); 122 | } 123 | } 124 | 125 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /src/paramparser.cpp: -------------------------------------------------------------------------------- 1 | #include "paramparser.h" 2 | #include "stream.h" 3 | #include "utils.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace figcone::shoal::detail { 11 | 12 | namespace { 13 | 14 | void skipParamWhitespace(Stream& stream, const std::string& paramName) 15 | { 16 | skipWhitespace(stream, false); 17 | if (stream.peek() == "\n") 18 | throw ConfigError{ 19 | "Wrong param '" + paramName + "' format: parameter's value must be placed on the same line as its name", 20 | stream.position()}; 21 | } 22 | 23 | std::optional readSingleParam( 24 | Stream& stream, 25 | const std::string& wordSeparator, 26 | const std::vector& paramListValue, 27 | const std::string& paramName, 28 | bool isMultiline) 29 | { 30 | if (isMultiline) 31 | stream.skipComments(false); 32 | const auto restoreSkipOnExit = eel::final_action{ 33 | [&] 34 | { 35 | if (isMultiline) 36 | stream.skipComments(true); 37 | }}; 38 | 39 | if (const auto quotedParam = readQuotedString(stream)) 40 | return *quotedParam; 41 | 42 | auto result = eel::trim(readUntil(stream, wordSeparator + "\n")); 43 | if (result.empty()) { 44 | if (stream.peek() == "," || (paramListValue.empty() && !isMultiline)) 45 | throw ConfigError{"Parameter list '" + paramName + "' element is missing", stream.position()}; 46 | if (paramListValue.empty() && isMultiline) 47 | return {}; 48 | } 49 | return result; 50 | } 51 | 52 | figcone::TreeParam makeParam( 53 | const std::vector& paramValueList, 54 | const StreamPosition& position, 55 | bool isList) 56 | { 57 | if (isList) 58 | return figcone::TreeParam{paramValueList, position}; 59 | else 60 | return figcone::TreeParam{paramValueList.at(0), position}; 61 | } 62 | 63 | figcone::TreeParam readParamOrParamList( 64 | Stream& stream, 65 | const std::string& paramName, 66 | StreamPosition pos, 67 | bool isMultiline = false) 68 | { 69 | auto paramValueList = std::vector{}; 70 | auto isList = isMultiline; 71 | while (!stream.atEnd()) { 72 | auto paramValue = readSingleParam(stream, isMultiline ? ",]" : ",", paramValueList, paramName, isMultiline); 73 | if (paramValue) 74 | paramValueList.emplace_back(std::move(*paramValue)); 75 | 76 | skipWhitespace(stream, isMultiline); 77 | const auto endOfList = isMultiline ? "]" : "\n"; 78 | if (stream.peek() == ",") { 79 | isList = true; 80 | stream.skip(1); 81 | skipWhitespace(stream, isMultiline); 82 | if (stream.peek() == endOfList || stream.atEnd()) 83 | throw ConfigError{"Parameter list '" + paramName + "' element is missing", stream.position()}; 84 | } 85 | else if (stream.peek() == endOfList) { 86 | stream.skip(1); 87 | return makeParam(paramValueList, pos, isList); 88 | } 89 | else if (stream.atEnd()) 90 | return makeParam(paramValueList, pos, isList); 91 | else 92 | throw ConfigError{ 93 | "Wrong param '" + paramName + "' format: there must be only one parameter per line", 94 | stream.position()}; 95 | } 96 | return makeParam(paramValueList, pos, isList); 97 | } 98 | 99 | figcone::TreeParam readParamValue(Stream& stream, const std::string& paramName, StreamPosition pos) 100 | { 101 | skipWhitespace(stream, false); 102 | if (stream.peek() == "\n" || stream.atEnd()) 103 | throw ConfigError{"Parameter '" + paramName + "' value is missing", stream.position()}; 104 | 105 | if (stream.peek() == "[") { 106 | stream.skip(1); 107 | skipWhitespace(stream); 108 | return readParamOrParamList(stream, paramName, pos, true); 109 | } 110 | 111 | return readParamOrParamList(stream, paramName, pos, false); 112 | } 113 | 114 | } //namespace 115 | 116 | std::pair parseParam(Stream& stream) 117 | { 118 | skipWhitespace(stream); 119 | const auto paramPos = stream.position(); 120 | auto paramName = readWord(stream, "="); 121 | if (paramName.empty()) 122 | throw ConfigError{"Parameter's name can't be empty", paramPos}; 123 | 124 | skipParamWhitespace(stream, paramName); 125 | 126 | const auto pos = stream.position(); 127 | if (stream.read() != "=") 128 | throw ConfigError{"Wrong param '" + paramName + "' format: missing '='", pos}; 129 | 130 | skipParamWhitespace(stream, paramName); 131 | return {paramName, readParamValue(stream, paramName, paramPos)}; 132 | } 133 | 134 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /src/nodeparser.cpp: -------------------------------------------------------------------------------- 1 | #include "nodeparser.h" 2 | #include "paramparser.h" 3 | #include "stream.h" 4 | #include "utils.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace figcone::shoal::detail { 10 | 11 | std::string readNodeName(Stream& stream) 12 | { 13 | const auto firstChar = stream.read(); 14 | eel::precondition(firstChar == "#", FIGCONE_SHOAL_EEL_LINE); 15 | 16 | auto nodeName = std::string{}; 17 | while (!stream.atEnd()) { 18 | if (stream.peek() == "\n") 19 | throw ConfigError{"Config node can't have a multiline name", stream.position()}; 20 | 21 | if (stream.peek() == ":") { 22 | stream.skip(1); 23 | const auto pos = stream.position(); 24 | if (!isBlank(readUntil(stream, "\n"))) 25 | throw ConfigError{ 26 | "Wrong config node '" + nodeName + 27 | "' format: only whitespaces and comments can be placed " 28 | "on the same line with config node's name.", 29 | pos}; 30 | break; 31 | } 32 | nodeName += stream.read(); 33 | } 34 | return nodeName; 35 | } 36 | 37 | ConfigReadResult readEndToken(Stream& stream) 38 | { 39 | stream.skip(1); 40 | if (stream.atEnd() || std::isspace(stream.peek().front())) 41 | return {ConfigReadResult::NextAction::ReturnToParentNode, {}, {}}; 42 | 43 | if (stream.peek(2) == "--") { 44 | stream.skip(2); 45 | if (!stream.peek().empty() && !std::isspace(stream.peek().front())) 46 | throw ConfigError{"Invalid closing token '---" + stream.peek() + "'", stream.position()}; 47 | 48 | return {ConfigReadResult::NextAction::ReturnToRootNode, {}, {}}; 49 | } 50 | 51 | const auto pos = stream.position(); 52 | const auto nextChar = stream.read(); 53 | if (nextChar != "-") 54 | throw ConfigError{"Invalid closing token '-" + nextChar + "'", pos}; 55 | 56 | const auto parentConfigNode = readWord(stream); 57 | return {ConfigReadResult::NextAction::ReturnToNodeByName, parentConfigNode, pos}; 58 | } 59 | 60 | ConfigReadResult checkReadResult( 61 | const ConfigReadResult& readResult, 62 | const std::string& newNodeName, 63 | const figcone::TreeNode& parentNode) 64 | { 65 | if (readResult.nextAction == ConfigReadResult::NextAction::ReturnToRootNode) 66 | return parentNode.isRoot() ? ConfigReadResult{ConfigReadResult::NextAction::ContinueReading, {}, {}} 67 | : readResult; 68 | 69 | if (readResult.nextAction == ConfigReadResult::NextAction::ReturnToParentNode && parentNode.isList()) { 70 | if (parentNode.isRoot()) 71 | throw ConfigError{"Can't close root node", readResult.returnToNodeStreamPosition}; 72 | else 73 | return readResult; 74 | } 75 | 76 | if (readResult.nextAction == ConfigReadResult::NextAction::ReturnToNodeByName) { 77 | if (newNodeName != readResult.parentNodeName) { 78 | if (parentNode.isRoot()) 79 | throw ConfigError{ 80 | "Can't close unexisting node '" + readResult.parentNodeName + "'", 81 | readResult.returnToNodeStreamPosition}; 82 | else 83 | return readResult; 84 | } 85 | else if (parentNode.isList()) 86 | return {ConfigReadResult::NextAction::ReturnToParentNode, {}, {}}; 87 | } 88 | return {ConfigReadResult::NextAction::ContinueReading, {}, {}}; 89 | } 90 | 91 | std::optional parseListElementNodeSection( 92 | Stream& stream, 93 | figcone::TreeNode& parent, 94 | const std::string& parentName) 95 | { 96 | if (!parent.isList()) 97 | return ConfigReadResult{ConfigReadResult::NextAction::ContinueReading, {}, {}}; 98 | 99 | stream.skip(3); 100 | skipWhitespace(stream, false); 101 | if (stream.atEnd()) 102 | return ConfigReadResult{ConfigReadResult::NextAction::ReturnToRootNode, {}, {}}; 103 | 104 | if (stream.peek() != "\n") 105 | throw ConfigError{ 106 | "Wrong config node list '" + parentName + 107 | "' format:" 108 | " there can't be anything besides comments and whitespaces " 109 | "on the same line with list separator '###'", 110 | stream.position()}; 111 | 112 | skipWhitespace(stream, true); 113 | const auto readResult = [&]()->ConfigReadResult{ 114 | if (stream.atEnd()) 115 | return {ConfigReadResult::NextAction::ReturnToRootNode, {}, {}}; 116 | else if (stream.peek() == "-") 117 | return readEndToken(stream); 118 | else { 119 | auto& nodeList = parent.asList(); 120 | auto& newNode = nodeList.emplaceBack(stream.position()); 121 | return parseNode(stream, newNode, parentName); 122 | } 123 | }(); 124 | 125 | auto result = checkReadResult(readResult, parentName, parent); 126 | if (result.nextAction != ConfigReadResult::NextAction::ContinueReading) { 127 | if (result.nextAction == ConfigReadResult::NextAction::ReturnToParentNode) 128 | result.nextAction = ConfigReadResult::NextAction::ContinueReading; 129 | return result; 130 | } 131 | return {}; 132 | } 133 | 134 | std::optional parseNodeSection(Stream& stream, figcone::TreeNode& parent) 135 | { 136 | const auto pos = stream.position(); 137 | const auto newNodeName = readNodeName(stream); 138 | if (isBlank(newNodeName)) 139 | throw ConfigError{"Config node name can't be blank", pos}; 140 | skipWhitespace(stream); 141 | 142 | if (parent.asItem().hasNode(newNodeName)) 143 | throw ConfigError{"Config node '" + newNodeName + "' already exist", pos}; 144 | auto& newNode = [&]() -> decltype(auto) 145 | { 146 | if (stream.peek(3) == "###") 147 | return parent.asItem().addNodeList(newNodeName, pos); 148 | else 149 | return parent.asItem().addNode(newNodeName, pos); 150 | }(); 151 | 152 | const auto readResult = parseNode(stream, newNode, newNodeName); 153 | auto result = checkReadResult(readResult, newNodeName, parent); 154 | if (result.nextAction != ConfigReadResult::NextAction::ContinueReading) { 155 | if (result.nextAction == ConfigReadResult::NextAction::ReturnToParentNode) 156 | result.nextAction = ConfigReadResult::NextAction::ContinueReading; 157 | return result; 158 | } 159 | return {}; 160 | } 161 | 162 | ConfigReadResult parseNode(Stream& stream, figcone::TreeNode& node, const std::string& nodeName) 163 | { 164 | while (!stream.atEnd()) { 165 | const auto nextChar = stream.peek().front(); 166 | if (std::isspace(nextChar)) 167 | stream.skip(1); 168 | else if (stream.peek(3) == "###") { 169 | if (auto res = parseListElementNodeSection(stream, node, nodeName)) 170 | return *res; 171 | } 172 | else if (nextChar == '#') { 173 | if (auto res = parseNodeSection(stream, node)) 174 | return *res; 175 | } 176 | else if (nextChar == '-') 177 | return readEndToken(stream); 178 | else { 179 | const auto [paramName, param] = parseParam(stream); 180 | if (param.isItem()) 181 | node.asItem().addParam(paramName, param.value()); 182 | else 183 | node.asItem().addParamList(paramName, param.valueList()); 184 | } 185 | } 186 | return {ConfigReadResult::NextAction::ReturnToRootNode, {}, {}}; 187 | } 188 | 189 | } //namespace figcone::shoal::detail -------------------------------------------------------------------------------- /tests/test_paramparser.cpp: -------------------------------------------------------------------------------- 1 | #include "assert_exception.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace test_paramparser { 9 | 10 | auto parseParam(const std::string& str) 11 | { 12 | auto input = std::stringstream{str}; 13 | auto stream = figcone::shoal::detail::Stream{input}; 14 | return figcone::shoal::detail::parseParam(stream); 15 | } 16 | 17 | TEST(TestParamParser, ParamWhitespaces) 18 | { 19 | auto [paramName, param] = parseParam(" \ntest = 1 \n"); 20 | ASSERT_TRUE(param.isItem()); 21 | EXPECT_EQ(paramName, "test"); 22 | EXPECT_EQ(param.value(), "1"); 23 | } 24 | 25 | TEST(TestParamParser, ParamWhitespacesCR) 26 | { 27 | auto [paramName, param] = parseParam(" \rtest = 1 \r"); 28 | ASSERT_TRUE(param.isItem()); 29 | EXPECT_EQ(paramName, "test"); 30 | EXPECT_EQ(param.value(), "1"); 31 | } 32 | 33 | 34 | TEST(TestParamParser, ParamWhitespacesCRLF) 35 | { 36 | auto [paramName, param] = parseParam(" \r\ntest = 1 \r\n"); 37 | ASSERT_TRUE(param.isItem()); 38 | EXPECT_EQ(paramName, "test"); 39 | EXPECT_EQ(param.value(), "1"); 40 | } 41 | 42 | 43 | TEST(TestParamParser, IntParam) 44 | { 45 | auto [paramName, param] = parseParam("test=1"); 46 | ASSERT_TRUE(param.isItem()); 47 | EXPECT_EQ(paramName, "test"); 48 | EXPECT_EQ(param.value(), "1"); 49 | } 50 | 51 | TEST(TestParamParser, StringParam) 52 | { 53 | auto [paramName, param] = parseParam("test='hello world.txt'"); 54 | ASSERT_TRUE(param.isItem()); 55 | EXPECT_EQ(paramName, "test"); 56 | EXPECT_EQ(param.value(), "hello world.txt"); 57 | } 58 | 59 | TEST(TestParamParser, StringParam2) 60 | { 61 | auto [paramName, param] = parseParam("test=\"'hello world.txt'\""); 62 | ASSERT_TRUE(param.isItem()); 63 | EXPECT_EQ(paramName, "test"); 64 | EXPECT_EQ(param.value(), "'hello world.txt'"); 65 | } 66 | 67 | TEST(TestParamParser, StringParam3) 68 | { 69 | auto [paramName, param] = parseParam("test=`'hello' \"world\"`"); 70 | ASSERT_TRUE(param.isItem()); 71 | EXPECT_EQ(paramName, "test"); 72 | EXPECT_EQ(param.value(), "'hello' \"world\""); 73 | } 74 | 75 | 76 | TEST(TestParamParser, EmptyStringParam) 77 | { 78 | auto [paramName, param] = parseParam("test=''"); 79 | ASSERT_TRUE(param.isItem()); 80 | EXPECT_EQ(paramName, "test"); 81 | EXPECT_EQ(param.value(), ""); 82 | } 83 | 84 | TEST(TestParamParser, Multiword) 85 | { 86 | auto [paramName, param] = parseParam("test='hello world'"); 87 | ASSERT_TRUE(param.isItem()); 88 | EXPECT_EQ(paramName, "test"); 89 | EXPECT_EQ(param.value(), "hello world"); 90 | } 91 | 92 | TEST(TestParamParser, Multiword2) 93 | { 94 | auto [paramName, param] = parseParam("test='hello\n world'"); 95 | ASSERT_TRUE(param.isItem()); 96 | EXPECT_EQ(paramName, "test"); 97 | EXPECT_EQ(param.value(), "hello\n world"); 98 | } 99 | 100 | TEST(TestParamParser, Multiword2CR) 101 | { 102 | auto [paramName, param] = parseParam("test='hello\r world'"); 103 | ASSERT_TRUE(param.isItem()); 104 | EXPECT_EQ(paramName, "test"); 105 | EXPECT_EQ(param.value(), "hello\n world"); 106 | } 107 | 108 | TEST(TestParamParser, Multiword2CRLF) 109 | { 110 | auto [paramName, param] = parseParam("test='hello\r\n world'"); 111 | ASSERT_TRUE(param.isItem()); 112 | EXPECT_EQ(paramName, "test"); 113 | EXPECT_EQ(param.value(), "hello\n world"); 114 | } 115 | 116 | TEST(TestParamParser, Multiword3) 117 | { 118 | auto [paramName, param] = parseParam("test=\"hello\n world\""); 119 | ASSERT_TRUE(param.isItem()); 120 | EXPECT_EQ(paramName, "test"); 121 | EXPECT_EQ(param.value(), "hello\n world"); 122 | } 123 | 124 | TEST(TestParamParser, Multiword3CR) 125 | { 126 | auto [paramName, param] = parseParam("test=\"hello\r world\""); 127 | ASSERT_TRUE(param.isItem()); 128 | EXPECT_EQ(paramName, "test"); 129 | EXPECT_EQ(param.value(), "hello\n world"); 130 | } 131 | 132 | TEST(TestParamParser, Multiword3CRLF) 133 | { 134 | auto [paramName, param] = parseParam("test=\"hello\r\n world\""); 135 | ASSERT_TRUE(param.isItem()); 136 | EXPECT_EQ(paramName, "test"); 137 | EXPECT_EQ(param.value(), "hello\n world"); 138 | } 139 | 140 | 141 | TEST(TestParamParser, Multiword4) 142 | { 143 | auto [paramName, param] = parseParam("test= hello world "); 144 | ASSERT_TRUE(param.isItem()); 145 | EXPECT_EQ(paramName, "test"); 146 | EXPECT_EQ(param.value(), "hello world"); 147 | } 148 | 149 | TEST(TestParamParser, Multiword5) 150 | { 151 | auto [paramName, param] = parseParam("test=\"\nhello world \" "); 152 | ASSERT_TRUE(param.isItem()); 153 | EXPECT_EQ(paramName, "test"); 154 | EXPECT_EQ(param.value(), "hello world "); 155 | } 156 | 157 | TEST(TestParamParser, Multiword5CR) 158 | { 159 | auto [paramName, param] = parseParam("test=\"\rhello world \" "); 160 | ASSERT_TRUE(param.isItem()); 161 | EXPECT_EQ(paramName, "test"); 162 | EXPECT_EQ(param.value(), "hello world "); 163 | } 164 | 165 | TEST(TestParamParser, Multiword5CRLF) 166 | { 167 | auto [paramName, param] = parseParam("test=\"\r\nhello world \" "); 168 | ASSERT_TRUE(param.isItem()); 169 | EXPECT_EQ(paramName, "test"); 170 | EXPECT_EQ(param.value(), "hello world "); 171 | } 172 | 173 | TEST(TestParamParser, ParamWithoutAssignmentError) 174 | { 175 | assert_exception( 176 | [&] 177 | { 178 | parseParam("test"); 179 | }, 180 | [](const figcone::ConfigError& error) 181 | { 182 | EXPECT_EQ(std::string{error.what()}, "[line:1, column:5] Wrong param 'test' format: missing '='"); 183 | }); 184 | } 185 | 186 | TEST(TestParamParser, ParamWithoutValueError) 187 | { 188 | assert_exception( 189 | [&] 190 | { 191 | parseParam("test ="); 192 | }, 193 | [](const figcone::ConfigError& error) 194 | { 195 | EXPECT_EQ(std::string{error.what()}, "[line:1, column:7] Parameter 'test' value is missing"); 196 | }); 197 | } 198 | 199 | TEST(TestParamParser, MultiwordParamNameError) 200 | { 201 | assert_exception( 202 | [&] 203 | { 204 | parseParam("test error= 1"); 205 | }, 206 | [](const figcone::ConfigError& error) 207 | { 208 | EXPECT_EQ(std::string{error.what()}, "[line:1, column:6] Wrong param 'test' format: missing '='"); 209 | }); 210 | } 211 | 212 | TEST(TestParamParser, MultilineParamNameError) 213 | { 214 | assert_exception( 215 | [&] 216 | { 217 | parseParam("test \n=1"); 218 | }, 219 | [](const figcone::ConfigError& error) 220 | { 221 | EXPECT_EQ( 222 | std::string{error.what()}, 223 | "[line:1, column:6] Wrong param 'test' format: parameter's value must be placed on the same " 224 | "line as its name"); 225 | }); 226 | } 227 | 228 | TEST(TestParamParser, MultilineParamNameErrorCR) 229 | { 230 | assert_exception( 231 | [&] 232 | { 233 | parseParam("test \r=1"); 234 | }, 235 | [](const figcone::ConfigError& error) 236 | { 237 | EXPECT_EQ( 238 | std::string{error.what()}, 239 | "[line:1, column:6] Wrong param 'test' format: parameter's value must be placed on the same " 240 | "line as its name"); 241 | }); 242 | } 243 | 244 | TEST(TestParamParser, MultilineParamNameErrorCRLF) 245 | { 246 | assert_exception( 247 | [&] 248 | { 249 | parseParam("test \r\n=1"); 250 | }, 251 | [](const figcone::ConfigError& error) 252 | { 253 | EXPECT_EQ( 254 | std::string{error.what()}, 255 | "[line:1, column:6] Wrong param 'test' format: parameter's value must be placed on the same " 256 | "line as its name"); 257 | }); 258 | } 259 | 260 | TEST(TestParamParser, MultilineParamError) 261 | { 262 | assert_exception( 263 | [&] 264 | { 265 | parseParam("test = \n1"); 266 | }, 267 | [](const figcone::ConfigError& error) 268 | { 269 | EXPECT_EQ( 270 | std::string{error.what()}, 271 | "[line:1, column:8] Wrong param 'test' format: " 272 | "parameter's value must be placed on the same line as its name"); 273 | }); 274 | } 275 | 276 | TEST(TestParamParser, MultilineParamErrorCR) 277 | { 278 | assert_exception( 279 | [&] 280 | { 281 | parseParam("test = \r1"); 282 | }, 283 | [](const figcone::ConfigError& error) 284 | { 285 | EXPECT_EQ( 286 | std::string{error.what()}, 287 | "[line:1, column:8] Wrong param 'test' format: " 288 | "parameter's value must be placed on the same line as its name"); 289 | }); 290 | } 291 | 292 | TEST(TestParamParser, MultilineParamErrorCRLF) 293 | { 294 | assert_exception( 295 | [&] 296 | { 297 | parseParam("test = \r\n1"); 298 | }, 299 | [](const figcone::ConfigError& error) 300 | { 301 | EXPECT_EQ( 302 | std::string{error.what()}, 303 | "[line:1, column:8] Wrong param 'test' format: " 304 | "parameter's value must be placed on the same line as its name"); 305 | }); 306 | } 307 | 308 | TEST(TestParamParser, UnclosedStringParamError) 309 | { 310 | assert_exception( 311 | [&] 312 | { 313 | parseParam("test='Hello "); 314 | }, 315 | [](const figcone::ConfigError& error) 316 | { 317 | EXPECT_EQ(std::string{error.what()}, "[line:1, column:6] String isn't closed"); 318 | }); 319 | } 320 | 321 | } //namespace test_paramparser 322 | -------------------------------------------------------------------------------- /tests/test_nodeparser.cpp: -------------------------------------------------------------------------------- 1 | #include "assert_exception.h" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace test_nodeparser { 8 | 9 | auto parse(const std::string& str) 10 | { 11 | auto input = std::stringstream{str}; 12 | auto parser = figcone::shoal::Parser{}; 13 | return parser.parse(input); 14 | } 15 | 16 | TEST(TestNodeParser, SingleNodeSingleLevel) 17 | { 18 | auto result = parse(R"( 19 | foo = 5 20 | bar = test 21 | #a: 22 | testInt = 10 23 | )"); 24 | 25 | auto& tree = result.root().asItem(); 26 | ASSERT_EQ(tree.paramsCount(), 2); 27 | ASSERT_EQ(tree.hasParam("foo"), 1); 28 | ASSERT_EQ(tree.hasParam("bar"), 1); 29 | EXPECT_EQ(tree.param("foo").value(), "5"); 30 | EXPECT_EQ(tree.param("bar").value(), "test"); 31 | ASSERT_EQ(tree.nodesCount(), 1); 32 | ASSERT_EQ(tree.hasNode("a"), 1); 33 | auto& aNode = tree.node("a").asItem(); 34 | ASSERT_EQ(aNode.paramsCount(), 1); 35 | EXPECT_EQ(aNode.param("testInt").value(), "10"); 36 | } 37 | 38 | TEST(TestNodeParser, SingleNodeSingleLevelCR) 39 | { 40 | auto result = parse( 41 | "foo = 5\r" 42 | "bar = test\r" 43 | "#a:\r" 44 | " testInt = 10\r" 45 | ); 46 | 47 | auto& tree = result.root().asItem(); 48 | ASSERT_EQ(tree.paramsCount(), 2); 49 | ASSERT_EQ(tree.hasParam("foo"), 1); 50 | ASSERT_EQ(tree.hasParam("bar"), 1); 51 | EXPECT_EQ(tree.param("foo").value(), "5"); 52 | EXPECT_EQ(tree.param("bar").value(), "test"); 53 | ASSERT_EQ(tree.nodesCount(), 1); 54 | ASSERT_EQ(tree.hasNode("a"), 1); 55 | auto& aNode = tree.node("a").asItem(); 56 | ASSERT_EQ(aNode.paramsCount(), 1); 57 | EXPECT_EQ(aNode.param("testInt").value(), "10"); 58 | } 59 | 60 | 61 | TEST(TestNodeParser, SingleNodeSingleLevelCRLF) 62 | { 63 | auto result = parse( 64 | "foo = 5\r\n" 65 | "bar = test\r\n" 66 | "#a:\r\n" 67 | " testInt = 10\r\n" 68 | ); 69 | 70 | auto& tree = result.root().asItem(); 71 | ASSERT_EQ(tree.paramsCount(), 2); 72 | ASSERT_EQ(tree.hasParam("foo"), 1); 73 | ASSERT_EQ(tree.hasParam("bar"), 1); 74 | EXPECT_EQ(tree.param("foo").value(), "5"); 75 | EXPECT_EQ(tree.param("bar").value(), "test"); 76 | ASSERT_EQ(tree.nodesCount(), 1); 77 | ASSERT_EQ(tree.hasNode("a"), 1); 78 | auto& aNode = tree.node("a").asItem(); 79 | ASSERT_EQ(aNode.paramsCount(), 1); 80 | EXPECT_EQ(aNode.param("testInt").value(), "10"); 81 | } 82 | 83 | TEST(TestNodeParser, MultiNodeSingleLevel) 84 | { 85 | 86 | auto result = parse(R"( 87 | foo = 5 88 | bar = test 89 | #a: 90 | testInt = 10 91 | - 92 | #b: 93 | testInt = 11 94 | )"); 95 | 96 | auto& tree = result.root().asItem(); 97 | ASSERT_EQ(tree.paramsCount(), 2); 98 | ASSERT_EQ(tree.hasParam("foo"), 1); 99 | ASSERT_EQ(tree.hasParam("bar"), 1); 100 | EXPECT_EQ(tree.param("foo").value(), "5"); 101 | EXPECT_EQ(tree.param("bar").value(), "test"); 102 | ASSERT_EQ(tree.nodesCount(), 2); 103 | ASSERT_EQ(tree.hasNode("a"), 1); 104 | ASSERT_EQ(tree.hasNode("b"), 1); 105 | auto& aNode = tree.node("a").asItem(); 106 | ASSERT_EQ(aNode.paramsCount(), 1); 107 | EXPECT_EQ(aNode.param("testInt").value(), "10"); 108 | auto& bNode = tree.node("b").asItem(); 109 | ASSERT_EQ(bNode.paramsCount(), 1); 110 | EXPECT_EQ(bNode.param("testInt").value(), "11"); 111 | } 112 | 113 | TEST(TestNodeParser, MultiLevel) 114 | { 115 | auto result = parse(R"( 116 | foo = 5 117 | bar = test 118 | #c: 119 | testInt = 11 120 | testDouble = 12 121 | #b: 122 | testInt = 10 123 | testString = 'Hello world' 124 | --- 125 | #b: 126 | testInt = 9 127 | )"); 128 | 129 | auto& tree = result.root().asItem(); 130 | ASSERT_EQ(tree.paramsCount(), 2); 131 | ASSERT_EQ(tree.hasParam("foo"), 1); 132 | ASSERT_EQ(tree.hasParam("bar"), 1); 133 | EXPECT_EQ(tree.param("foo").value(), "5"); 134 | EXPECT_EQ(tree.param("bar").value(), "test"); 135 | ASSERT_EQ(tree.nodesCount(), 2); 136 | ASSERT_EQ(tree.hasNode("c"), 1); 137 | ASSERT_EQ(tree.hasNode("b"), 1); 138 | 139 | auto& cNode = tree.node("c").asItem(); 140 | ASSERT_EQ(cNode.paramsCount(), 2); 141 | EXPECT_EQ(cNode.param("testInt").value(), "11"); 142 | EXPECT_EQ(cNode.param("testDouble").value(), "12"); 143 | ASSERT_EQ(cNode.nodesCount(), 1); 144 | ASSERT_EQ(cNode.hasNode("b"), 1); 145 | auto& cbNode = cNode.node("b").asItem(); 146 | ASSERT_EQ(cbNode.paramsCount(), 2); 147 | EXPECT_EQ(cbNode.param("testInt").value(), "10"); 148 | EXPECT_EQ(cbNode.param("testString").value(), "Hello world"); 149 | 150 | auto& bNode = tree.node("b").asItem(); 151 | ASSERT_EQ(bNode.paramsCount(), 1); 152 | EXPECT_EQ(bNode.param("testInt").value(), "9"); 153 | } 154 | 155 | TEST(TestNodeParser, MultiLevelWithComments) 156 | { 157 | auto result = parse(R"( 158 | foo = 5; 159 | bar = test; 160 | #c: ;test comment#1 161 | testInt = 11 ;test comment#2 162 | testDouble = 12 163 | ;test comment#3 164 | #b: ;test comment#4 165 | testInt = 10 166 | testString = 'Hello; world' 167 | --b; test comment#5 168 | ---;test comment#6 169 | #b: 170 | testInt = 9 171 | - ;test comment#7 172 | )"); 173 | 174 | auto& tree = result.root().asItem(); 175 | ASSERT_EQ(tree.paramsCount(), 2); 176 | ASSERT_EQ(tree.hasParam("foo"), 1); 177 | ASSERT_EQ(tree.hasParam("bar"), 1); 178 | EXPECT_EQ(tree.param("foo").value(), "5"); 179 | EXPECT_EQ(tree.param("bar").value(), "test"); 180 | ASSERT_EQ(tree.nodesCount(), 2); 181 | ASSERT_EQ(tree.hasNode("c"), 1); 182 | ASSERT_EQ(tree.hasNode("b"), 1); 183 | 184 | auto& cNode = tree.node("c").asItem(); 185 | ASSERT_EQ(cNode.paramsCount(), 2); 186 | EXPECT_EQ(cNode.param("testInt").value(), "11"); 187 | EXPECT_EQ(cNode.param("testDouble").value(), "12"); 188 | ASSERT_EQ(cNode.nodesCount(), 1); 189 | ASSERT_EQ(cNode.hasNode("b"), 1); 190 | auto& cbNode = cNode.node("b").asItem(); 191 | ASSERT_EQ(cbNode.paramsCount(), 2); 192 | EXPECT_EQ(cbNode.param("testInt").value(), "10"); 193 | EXPECT_EQ(cbNode.param("testString").value(), "Hello; world"); 194 | 195 | auto& bNode = tree.node("b").asItem(); 196 | ASSERT_EQ(bNode.paramsCount(), 1); 197 | EXPECT_EQ(bNode.param("testInt").value(), "9"); 198 | } 199 | 200 | TEST(TestNodeParser, MultiLevelParamAfterNode) 201 | { 202 | auto result = parse(R"( 203 | foo = 5 204 | bar = test 205 | #c: 206 | testInt = 11 207 | #b: 208 | testInt = 10 209 | testString = 'Hello world' 210 | - 211 | testDouble = 12 212 | --- 213 | #b: 214 | testInt = 9 215 | )"); 216 | 217 | auto& tree = result.root().asItem(); 218 | ASSERT_EQ(tree.paramsCount(), 2); 219 | ASSERT_EQ(tree.hasParam("foo"), 1); 220 | ASSERT_EQ(tree.hasParam("bar"), 1); 221 | EXPECT_EQ(tree.param("foo").value(), "5"); 222 | EXPECT_EQ(tree.param("bar").value(), "test"); 223 | ASSERT_EQ(tree.nodesCount(), 2); 224 | ASSERT_EQ(tree.hasNode("c"), 1); 225 | ASSERT_EQ(tree.hasNode("b"), 1); 226 | auto& cNode = tree.node("c").asItem(); 227 | ASSERT_EQ(cNode.paramsCount(), 2); 228 | ASSERT_EQ(cNode.hasParam("testInt"), 1); 229 | EXPECT_EQ(cNode.param("testInt").value(), "11"); 230 | ASSERT_EQ(cNode.hasParam("testDouble"), 1); 231 | EXPECT_EQ(cNode.param("testDouble").value(), "12"); 232 | ASSERT_EQ(cNode.nodesCount(), 1); 233 | ASSERT_EQ(cNode.hasNode("b"), 1); 234 | auto& cbNode = cNode.node("b").asItem(); 235 | ASSERT_EQ(cbNode.paramsCount(), 2); 236 | EXPECT_EQ(cbNode.param("testInt").value(), "10"); 237 | EXPECT_EQ(cbNode.param("testString").value(), "Hello world"); 238 | auto& bNode = tree.node("b").asItem(); 239 | ASSERT_EQ(bNode.paramsCount(), 1); 240 | EXPECT_EQ(bNode.param("testInt").value(), "9"); 241 | } 242 | 243 | TEST(TestNodeParser, MultiLevelCloseByName) 244 | { 245 | auto result = parse(R"( 246 | foo = 5 247 | bar = test 248 | #c: 249 | testInt = 11 250 | testDouble = 12 251 | #b: 252 | testInt = 10 253 | testString = 'Hello world' 254 | --c 255 | #b: 256 | testInt = 9 257 | )"); 258 | 259 | auto& tree = result.root().asItem(); 260 | ASSERT_EQ(tree.paramsCount(), 2); 261 | ASSERT_EQ(tree.hasParam("foo"), 1); 262 | ASSERT_EQ(tree.hasParam("bar"), 1); 263 | EXPECT_EQ(tree.param("foo").value(), "5"); 264 | EXPECT_EQ(tree.param("bar").value(), "test"); 265 | ASSERT_EQ(tree.nodesCount(), 2); 266 | ASSERT_EQ(tree.hasNode("c"), 1); 267 | ASSERT_EQ(tree.hasNode("b"), 1); 268 | auto& cNode = tree.node("c").asItem(); 269 | ASSERT_EQ(cNode.paramsCount(), 2); 270 | ASSERT_EQ(cNode.hasParam("testInt"), 1); 271 | EXPECT_EQ(cNode.param("testInt").value(), "11"); 272 | ASSERT_EQ(cNode.hasParam("testDouble"), 1); 273 | EXPECT_EQ(cNode.param("testDouble").value(), "12"); 274 | ASSERT_EQ(cNode.nodesCount(), 1); 275 | ASSERT_EQ(cNode.hasNode("b"), 1); 276 | auto& cbNode = cNode.node("b").asItem(); 277 | ASSERT_EQ(cbNode.paramsCount(), 2); 278 | EXPECT_EQ(cbNode.param("testInt").value(), "10"); 279 | EXPECT_EQ(cbNode.param("testString").value(), "Hello world"); 280 | auto& bNode = tree.node("b").asItem(); 281 | ASSERT_EQ(bNode.paramsCount(), 1); 282 | EXPECT_EQ(bNode.param("testInt").value(), "9"); 283 | } 284 | 285 | TEST(TestNodeParser, MultiLevelClose) 286 | { 287 | auto result = parse(R"( 288 | foo = 5 289 | bar = test 290 | #c: 291 | testInt = 11 292 | testDouble = 12 293 | #b: 294 | testInt = 10 295 | testString = 'Hello world' 296 | - 297 | - 298 | #b: 299 | testInt = 9 300 | )"); 301 | 302 | auto& tree = result.root().asItem(); 303 | ASSERT_EQ(tree.paramsCount(), 2); 304 | ASSERT_EQ(tree.hasParam("foo"), 1); 305 | ASSERT_EQ(tree.hasParam("bar"), 1); 306 | EXPECT_EQ(tree.param("foo").value(), "5"); 307 | EXPECT_EQ(tree.param("bar").value(), "test"); 308 | ASSERT_EQ(tree.nodesCount(), 2); 309 | ASSERT_EQ(tree.hasNode("c"), 1); 310 | ASSERT_EQ(tree.hasNode("b"), 1); 311 | auto& cNode = tree.node("c").asItem(); 312 | ASSERT_EQ(cNode.paramsCount(), 2); 313 | ASSERT_EQ(cNode.hasParam("testInt"), 1); 314 | EXPECT_EQ(cNode.param("testInt").value(), "11"); 315 | ASSERT_EQ(cNode.hasParam("testDouble"), 1); 316 | EXPECT_EQ(cNode.param("testDouble").value(), "12"); 317 | ASSERT_EQ(cNode.nodesCount(), 1); 318 | ASSERT_EQ(cNode.hasNode("b"), 1); 319 | auto& cbNode = cNode.node("b").asItem(); 320 | ASSERT_EQ(cbNode.paramsCount(), 2); 321 | EXPECT_EQ(cbNode.param("testInt").value(), "10"); 322 | EXPECT_EQ(cbNode.param("testString").value(), "Hello world"); 323 | auto& bNode = tree.node("b").asItem(); 324 | ASSERT_EQ(bNode.paramsCount(), 1); 325 | EXPECT_EQ(bNode.param("testInt").value(), "9"); 326 | } 327 | 328 | TEST(TestNodeParser, CloseByUnknownNameError) 329 | { 330 | assert_exception( 331 | [&] 332 | { 333 | parse(R"( 334 | foo = 5 335 | bar = test 336 | #c: 337 | testInt = 11 338 | testDouble = 12 339 | #b: 340 | testInt = 10 341 | testString = 'Hello world' 342 | --test 343 | #b: 344 | testInt = 9 345 | )"); 346 | }, 347 | [](const figcone::ConfigError& error) 348 | { 349 | EXPECT_EQ(std::string{error.what()}, "[line:10, column:14] Can't close unexisting node 'test'"); 350 | }); 351 | } 352 | 353 | TEST(TestNodeParser, ReturnToRootClosingTokenError) 354 | { 355 | assert_exception( 356 | [&] 357 | { 358 | parse(R"( 359 | foo = 5 360 | bar = test 361 | #a: 362 | testInt = 10 363 | ---a 364 | )"); 365 | }, 366 | [](const figcone::ConfigError& error) 367 | { 368 | EXPECT_EQ(std::string{error.what()}, "[line:6, column:16] Invalid closing token '---a'"); 369 | }); 370 | } 371 | 372 | TEST(TestNodeParser, ReturnToParentClosingTokenError) 373 | { 374 | assert_exception( 375 | [&] 376 | { 377 | parse(R"( 378 | foo = 5 379 | bar = test 380 | #a: 381 | testInt = 10 382 | -a 383 | )"); 384 | }, 385 | [](const figcone::ConfigError& error) 386 | { 387 | EXPECT_EQ(std::string{error.what()}, "[line:6, column:14] Invalid closing token '-a'"); 388 | }); 389 | } 390 | 391 | TEST(TestNodeParser, InvalidNodeNameLineError) 392 | { 393 | assert_exception( 394 | [&] 395 | { 396 | parse(R"( 397 | foo = 5 398 | bar = test 399 | #a: test 400 | testInt = 10 401 | )"); 402 | }, 403 | [](const figcone::ConfigError& error) 404 | { 405 | EXPECT_EQ( 406 | std::string{error.what()}, 407 | "[line:4, column:16] Wrong config node 'a' format: " 408 | "only whitespaces and comments can be placed on the same line with config node's name."); 409 | }); 410 | } 411 | 412 | TEST(TestNodeParser, BlankNodeNameError) 413 | { 414 | assert_exception( 415 | [&] 416 | { 417 | parse(R"( 418 | foo = 5 419 | bar = test 420 | # : 421 | testInt = 10 422 | )"); 423 | }, 424 | [](const figcone::ConfigError& error) 425 | { 426 | EXPECT_EQ(std::string{error.what()}, "[line:4, column:13] Config node name can't be blank"); 427 | }); 428 | } 429 | 430 | TEST(TestNodeParser, MultilineNodeNameError) 431 | { 432 | assert_exception( 433 | [&] 434 | { 435 | parse(R"( 436 | foo = 5 437 | bar = test 438 | # 439 | b: 440 | testInt = 10 441 | )"); 442 | }, 443 | [](const figcone::ConfigError& error) 444 | { 445 | EXPECT_EQ(std::string{error.what()}, "[line:4, column:14] Config node can't have a multiline name"); 446 | }); 447 | } 448 | 449 | } //namespace test_nodeparser -------------------------------------------------------------------------------- /tests/test_paramlistparser.cpp: -------------------------------------------------------------------------------- 1 | #include "assert_exception.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace test_paramlistparser { 9 | 10 | auto parseParam(const std::string& str) 11 | { 12 | auto input = std::stringstream{str}; 13 | auto stream = figcone::shoal::detail::Stream{input}; 14 | return figcone::shoal::detail::parseParam(stream); 15 | } 16 | 17 | TEST(TestParamListParser, Basic) 18 | { 19 | auto [paramName, param] = parseParam("testIntList = 1, 2, 3"); 20 | 21 | ASSERT_TRUE(param.isList()); 22 | EXPECT_EQ(paramName, "testIntList"); 23 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 24 | } 25 | 26 | TEST(TestParamListParser, BasicWithoutMacro) 27 | { 28 | auto [paramName, param] = parseParam("testIntList = 1, 2, 3"); 29 | 30 | ASSERT_TRUE(param.isList()); 31 | EXPECT_EQ(paramName, "testIntList"); 32 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 33 | } 34 | 35 | TEST(TestParamListParser, BasicAltWhitespace) 36 | { 37 | auto [paramName, param] = parseParam("testIntList =1,2,3 "); 38 | 39 | ASSERT_TRUE(param.isList()); 40 | EXPECT_EQ(paramName, "testIntList"); 41 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 42 | } 43 | 44 | TEST(TestParamListParser, EmptyLastElementError) 45 | { 46 | assert_exception( 47 | [&] 48 | { 49 | parseParam("testIntList =1,2, "); 50 | }, 51 | [](const figcone::ConfigError& e) 52 | { 53 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:19] Parameter list 'testIntList' element is missing"); 54 | }); 55 | } 56 | 57 | TEST(TestParamListParser, EmptyFirstElementError) 58 | { 59 | assert_exception( 60 | [&] 61 | { 62 | parseParam("testIntList = ,1,2 "); 63 | }, 64 | [](const figcone::ConfigError& e) 65 | { 66 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:15] Parameter list 'testIntList' element is missing"); 67 | }); 68 | } 69 | 70 | TEST(TestParamListParser, EmptyElementError) 71 | { 72 | assert_exception( 73 | [&] 74 | { 75 | parseParam("testIntList = 1, ,3 "); 76 | }, 77 | [](const figcone::ConfigError& e) 78 | { 79 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:18] Parameter list 'testIntList' element is missing"); 80 | }); 81 | } 82 | 83 | TEST(TestParamListParser, EmptyLastElementAltWhitespaceError) 84 | { 85 | assert_exception( 86 | [&] 87 | { 88 | parseParam("testIntList =1,2, \n "); 89 | }, 90 | [](const figcone::ConfigError& e) 91 | { 92 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:19] Parameter list 'testIntList' element is missing"); 93 | }); 94 | } 95 | 96 | TEST(TestParamListParser, EmptyLastElementAltWhitespaceErrorCR) 97 | { 98 | assert_exception( 99 | [&] 100 | { 101 | parseParam("testIntList =1,2, \r "); 102 | }, 103 | [](const figcone::ConfigError& e) 104 | { 105 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:19] Parameter list 'testIntList' element is missing"); 106 | }); 107 | } 108 | 109 | TEST(TestParamListParser, EmptyLastElementAltWhitespaceErrorCRLF) 110 | { 111 | assert_exception( 112 | [&] 113 | { 114 | parseParam("testIntList =1,2, \r\n "); 115 | }, 116 | [](const figcone::ConfigError& e) 117 | { 118 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:19] Parameter list 'testIntList' element is missing"); 119 | }); 120 | } 121 | 122 | TEST(TestParamListParser, EmptyError) 123 | { 124 | assert_exception( 125 | [&] 126 | { 127 | parseParam("testIntList = "); 128 | }, 129 | [](const figcone::ConfigError& e) 130 | { 131 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:15] Parameter 'testIntList' value is missing"); 132 | }); 133 | } 134 | 135 | TEST(TestParamListParser, EmptyAltWhitespaceError) 136 | { 137 | assert_exception( 138 | [&] 139 | { 140 | parseParam("testIntList ="); 141 | }, 142 | [](const figcone::ConfigError& e) 143 | { 144 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:14] Parameter 'testIntList' value is missing"); 145 | }); 146 | } 147 | 148 | TEST(TestParamListParser, EmptyAltWhitespace2Error) 149 | { 150 | assert_exception( 151 | [&] 152 | { 153 | parseParam("testIntList = \n "); 154 | }, 155 | [](const figcone::ConfigError& e) 156 | { 157 | ASSERT_EQ( 158 | std::string{e.what()}, 159 | "[line:1, column:15] Wrong param 'testIntList' format: parameter's value must be placed on the " 160 | "same line as its name"); 161 | }); 162 | } 163 | 164 | TEST(TestParamListParser, EmptyAltWhitespace2ErrorCR) 165 | { 166 | assert_exception( 167 | [&] 168 | { 169 | parseParam("testIntList = \r "); 170 | }, 171 | [](const figcone::ConfigError& e) 172 | { 173 | ASSERT_EQ( 174 | std::string{e.what()}, 175 | "[line:1, column:15] Wrong param 'testIntList' format: parameter's value must be placed on the " 176 | "same line as its name"); 177 | }); 178 | } 179 | 180 | TEST(TestParamListParser, EmptyAltWhitespace2ErrorCRLF) 181 | { 182 | assert_exception( 183 | [&] 184 | { 185 | parseParam("testIntList = \r\n "); 186 | }, 187 | [](const figcone::ConfigError& e) 188 | { 189 | ASSERT_EQ( 190 | std::string{e.what()}, 191 | "[line:1, column:15] Wrong param 'testIntList' format: parameter's value must be placed on the " 192 | "same line as its name"); 193 | }); 194 | } 195 | 196 | TEST(TestParamListParser, EmptyWithSeparatorError) 197 | { 198 | assert_exception( 199 | [&] 200 | { 201 | parseParam("testIntList = ,"); 202 | }, 203 | [](const figcone::ConfigError& e) 204 | { 205 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:15] Parameter list 'testIntList' element is missing"); 206 | }); 207 | } 208 | 209 | TEST(TestParamListParser, EmptyWithSeparatorAltWhitespaceError) 210 | { 211 | assert_exception( 212 | [&] 213 | { 214 | parseParam("testIntList =, "); 215 | }, 216 | [](const figcone::ConfigError& e) 217 | { 218 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:14] Parameter list 'testIntList' element is missing"); 219 | }); 220 | } 221 | 222 | TEST(TestParamListParser, BasicString) 223 | { 224 | auto [paramName, param] = parseParam("testStrList = 'Hello,\n world', Foo "); 225 | 226 | ASSERT_TRUE(param.isList()); 227 | EXPECT_EQ(paramName, "testStrList"); 228 | EXPECT_EQ(param.valueList(), (std::vector{"Hello,\n world", "Foo"})); 229 | } 230 | 231 | TEST(TestParamListParser, BasicStringCR) 232 | { 233 | auto [paramName, param] = parseParam("testStrList = 'Hello,\r world', Foo "); 234 | 235 | ASSERT_TRUE(param.isList()); 236 | EXPECT_EQ(paramName, "testStrList"); 237 | EXPECT_EQ(param.valueList(), (std::vector{"Hello,\n world", "Foo"})); 238 | } 239 | 240 | TEST(TestParamListParser, BasicStringCRLF) 241 | { 242 | auto [paramName, param] = parseParam("testStrList = 'Hello,\r\n world', Foo "); 243 | 244 | ASSERT_TRUE(param.isList()); 245 | EXPECT_EQ(paramName, "testStrList"); 246 | EXPECT_EQ(param.valueList(), (std::vector{"Hello,\n world", "Foo"})); 247 | } 248 | 249 | TEST(TestParamListParser, BasicString2) 250 | { 251 | auto [paramName, param] = parseParam("testStrList = Hello world , Foo "); 252 | 253 | ASSERT_TRUE(param.isList()); 254 | EXPECT_EQ(paramName, "testStrList"); 255 | EXPECT_EQ(param.valueList(), (std::vector{"Hello world", "Foo"})); 256 | } 257 | 258 | TEST(TestParamListParser, Multiline) 259 | { 260 | auto [paramName, param] = parseParam("testIntList = [1,2,3]"); 261 | 262 | ASSERT_TRUE(param.isList()); 263 | EXPECT_EQ(paramName, "testIntList"); 264 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 265 | } 266 | 267 | TEST(TestParamListParser, MultilineWithString) 268 | { 269 | auto [paramName, param] = parseParam("testStrList = [\"Hello\",world, ' ']"); 270 | 271 | ASSERT_TRUE(param.isList()); 272 | EXPECT_EQ(paramName, "testStrList"); 273 | EXPECT_EQ(param.valueList(), (std::vector{"Hello", "world", " "})); 274 | } 275 | 276 | TEST(TestParamListParser, MultilineWithString2) 277 | { 278 | auto [paramName, param] = parseParam("testStrList = [Hello world, ' ']"); 279 | 280 | ASSERT_TRUE(param.isList()); 281 | EXPECT_EQ(paramName, "testStrList"); 282 | EXPECT_EQ(param.valueList(), (std::vector{"Hello world", " "})); 283 | } 284 | 285 | TEST(TestParamListParser, MultilineWithStringAltWhitespace) 286 | { 287 | auto [paramName, param] = parseParam("testStrList = [ \"Hello\"\n, world,\n ''\n]"); 288 | 289 | ASSERT_TRUE(param.isList()); 290 | EXPECT_EQ(paramName, "testStrList"); 291 | EXPECT_EQ(param.valueList(), (std::vector{"Hello", "world", ""})); 292 | } 293 | 294 | TEST(TestParamListParser, MultilineWithStringAltWhitespaceCR) 295 | { 296 | auto [paramName, param] = parseParam("testStrList = [ \"Hello\"\r, world,\r ''\r]"); 297 | 298 | ASSERT_TRUE(param.isList()); 299 | EXPECT_EQ(paramName, "testStrList"); 300 | EXPECT_EQ(param.valueList(), (std::vector{"Hello", "world", ""})); 301 | } 302 | 303 | TEST(TestParamListParser, MultilineWithStringAltWhitespaceCRLF) 304 | { 305 | auto [paramName, param] = parseParam("testStrList = [ \"Hello\"\r\n, world,\r\n ''\r\n]"); 306 | 307 | ASSERT_TRUE(param.isList()); 308 | EXPECT_EQ(paramName, "testStrList"); 309 | EXPECT_EQ(param.valueList(), (std::vector{"Hello", "world", ""})); 310 | } 311 | 312 | TEST(TestParamListParser, MultilineAltWhitespace) 313 | { 314 | auto [paramName, param] = parseParam("testIntList = [ 1, 2, 3 ]"); 315 | 316 | ASSERT_TRUE(param.isList()); 317 | EXPECT_EQ(paramName, "testIntList"); 318 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 319 | } 320 | 321 | TEST(TestParamListParser, MultilineAltWhitespace2) 322 | { 323 | auto [paramName, param] = parseParam("testIntList = [1,\n 2\n, 3 ]"); 324 | 325 | ASSERT_TRUE(param.isList()); 326 | EXPECT_EQ(paramName, "testIntList"); 327 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 328 | } 329 | 330 | TEST(TestParamListParser, MultilineAltWhitespace2CR) 331 | { 332 | auto [paramName, param] = parseParam("testIntList = [1,\r 2\r, 3 ]"); 333 | 334 | ASSERT_TRUE(param.isList()); 335 | EXPECT_EQ(paramName, "testIntList"); 336 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 337 | } 338 | 339 | TEST(TestParamListParser, MultilineAltWhitespace2CRLF) 340 | { 341 | auto [paramName, param] = parseParam("testIntList = [1,\r\n 2\r\n, 3 ]"); 342 | 343 | ASSERT_TRUE(param.isList()); 344 | EXPECT_EQ(paramName, "testIntList"); 345 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 346 | } 347 | 348 | 349 | TEST(TestParamListParser, MultilineAltWhitespace3) 350 | { 351 | auto [paramName, param] = parseParam("testIntList = [\n1\n,2,\n 3]"); 352 | 353 | ASSERT_TRUE(param.isList()); 354 | EXPECT_EQ(paramName, "testIntList"); 355 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 356 | } 357 | 358 | TEST(TestParamListParser, MultilineAltWhitespace3CR) 359 | { 360 | auto [paramName, param] = parseParam("testIntList = [\r1\r,2,\r 3]"); 361 | 362 | ASSERT_TRUE(param.isList()); 363 | EXPECT_EQ(paramName, "testIntList"); 364 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 365 | } 366 | 367 | TEST(TestParamListParser, MultilineAltWhitespace3CRLF) 368 | { 369 | auto [paramName, param] = parseParam("testIntList = [\r\n1\r\n,2,\r\n 3]"); 370 | 371 | ASSERT_TRUE(param.isList()); 372 | EXPECT_EQ(paramName, "testIntList"); 373 | EXPECT_EQ(param.valueList(), (std::vector{"1", "2", "3"})); 374 | } 375 | 376 | TEST(TestParamListParser, MultilineTest) 377 | { 378 | auto [paramName, param] = parseParam("testStrList = [mp4(h264), webm(vp8;vp9)]"); 379 | ASSERT_TRUE(param.isList()); 380 | EXPECT_EQ(paramName, "testStrList"); 381 | EXPECT_EQ(param.valueList(), (std::vector{"mp4(h264)", "webm(vp8;vp9)"})); 382 | } 383 | 384 | TEST(TestParamListParser, MultiLineEmptyFirstElementError) 385 | { 386 | assert_exception( 387 | [&] 388 | { 389 | parseParam("testIntList =[,2,3 ] "); 390 | }, 391 | [](const figcone::ConfigError& e) 392 | { 393 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:15] Parameter list 'testIntList' element is missing"); 394 | }); 395 | } 396 | 397 | TEST(TestParamListParser, MultiLineEmptyElementError) 398 | { 399 | assert_exception( 400 | [&] 401 | { 402 | parseParam("testIntList =[1, ,3 ] "); 403 | }, 404 | [](const figcone::ConfigError& e) 405 | { 406 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:18] Parameter list 'testIntList' element is missing"); 407 | }); 408 | } 409 | 410 | TEST(TestParamListParser, MultiLineEmptyLastElementError) 411 | { 412 | assert_exception( 413 | [&] 414 | { 415 | parseParam("testIntList =[1,2, ] "); 416 | }, 417 | [](const figcone::ConfigError& e) 418 | { 419 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:20] Parameter list 'testIntList' element is missing"); 420 | }); 421 | } 422 | 423 | TEST(TestParamListParser, MultiLineEmpty) 424 | { 425 | auto [paramName, param] = parseParam("testIntList = [] "); 426 | 427 | ASSERT_TRUE(param.isList()); 428 | EXPECT_EQ(paramName, "testIntList"); 429 | EXPECT_EQ(param.valueList(), (std::vector{})); 430 | } 431 | 432 | TEST(TestParamListParser, MultiLineEmptyAltWhitespace) 433 | { 434 | auto [paramName, param] = parseParam("testIntList = [ ] "); 435 | 436 | ASSERT_TRUE(param.isList()); 437 | EXPECT_EQ(paramName, "testIntList"); 438 | EXPECT_EQ(param.valueList(), (std::vector{})); 439 | } 440 | 441 | TEST(TestParamListParser, MultiLineEmptyAltWhitespace2) 442 | { 443 | auto [paramName, param] = parseParam("testIntList = [ \n ] "); 444 | 445 | ASSERT_TRUE(param.isList()); 446 | EXPECT_EQ(paramName, "testIntList"); 447 | EXPECT_EQ(param.valueList(), (std::vector{})); 448 | } 449 | 450 | TEST(TestParamListParser, MultiLineEmptyAltWhitespace2CR) 451 | { 452 | auto [paramName, param] = parseParam("testIntList = [ \r ] "); 453 | 454 | ASSERT_TRUE(param.isList()); 455 | EXPECT_EQ(paramName, "testIntList"); 456 | EXPECT_EQ(param.valueList(), (std::vector{})); 457 | } 458 | 459 | TEST(TestParamListParser, MultiLineEmptyAltWhitespace2CRLF) 460 | { 461 | auto [paramName, param] = parseParam("testIntList = [ \r\n ] "); 462 | 463 | ASSERT_TRUE(param.isList()); 464 | EXPECT_EQ(paramName, "testIntList"); 465 | EXPECT_EQ(param.valueList(), (std::vector{})); 466 | } 467 | 468 | 469 | TEST(TestParamListParser, MultiLineEmptyWithSeparatorError) 470 | { 471 | assert_exception( 472 | [&] 473 | { 474 | parseParam("testIntList =[,] "); 475 | }, 476 | [](const figcone::ConfigError& e) 477 | { 478 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:15] Parameter list 'testIntList' element is missing"); 479 | }); 480 | } 481 | 482 | TEST(TestParamListParser, MultiLineEmptyWithSeparatorAltWhitespaceError) 483 | { 484 | assert_exception( 485 | [&] 486 | { 487 | parseParam("testIntList =[ , ] "); 488 | }, 489 | [](const figcone::ConfigError& e) 490 | { 491 | ASSERT_EQ(std::string{e.what()}, "[line:1, column:17] Parameter list 'testIntList' element is missing"); 492 | }); 493 | } 494 | 495 | TEST(TestParamListParser, MultiLineEmptyWithSeparatorAltWhitespace2Error) 496 | { 497 | assert_exception( 498 | [&] 499 | { 500 | parseParam("testIntList =[ \n, \n] "); 501 | }, 502 | [](const figcone::ConfigError& e) 503 | { 504 | ASSERT_EQ(std::string{e.what()}, "[line:2, column:1] Parameter list 'testIntList' element is missing"); 505 | }); 506 | } 507 | 508 | TEST(TestParamListParser, MultiLineEmptyWithSeparatorAltWhitespace2ErrorCR) 509 | { 510 | assert_exception( 511 | [&] 512 | { 513 | parseParam("testIntList =[ \r, \r] "); 514 | }, 515 | [](const figcone::ConfigError& e) 516 | { 517 | ASSERT_EQ(std::string{e.what()}, "[line:2, column:1] Parameter list 'testIntList' element is missing"); 518 | }); 519 | } 520 | 521 | TEST(TestParamListParser, MultiLineEmptyWithSeparatorAltWhitespace2ErrorCRLF) 522 | { 523 | assert_exception( 524 | [&] 525 | { 526 | parseParam("testIntList =[ \r\n, \r\n] "); 527 | }, 528 | [](const figcone::ConfigError& e) 529 | { 530 | ASSERT_EQ(std::string{e.what()}, "[line:2, column:1] Parameter list 'testIntList' element is missing"); 531 | }); 532 | } 533 | 534 | 535 | } //namespace test_paramlistparser 536 | -------------------------------------------------------------------------------- /tests/test_nodelistparser.cpp: -------------------------------------------------------------------------------- 1 | #include "assert_exception.h" 2 | #include 3 | #include 4 | 5 | namespace test_nodelistparser { 6 | 7 | auto parse(const std::string& str) 8 | { 9 | auto input = std::stringstream{str}; 10 | auto parser = figcone::shoal::Parser{}; 11 | return parser.parse(input); 12 | } 13 | 14 | TEST(TestNodeListParser, Basic) 15 | { 16 | auto result = parse(R"( 17 | testStr = Hello 18 | #testNodes: 19 | ### 20 | testInt = 3 21 | ### 22 | testInt = 2 23 | )"); 24 | 25 | auto& tree = result.root().asItem(); 26 | ASSERT_EQ(tree.paramsCount(), 1); 27 | ASSERT_EQ(tree.hasParam("testStr"), 1); 28 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 29 | ASSERT_EQ(tree.nodesCount(), 1); 30 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 31 | auto& testNodes = tree.node("testNodes").asList(); 32 | ASSERT_EQ(testNodes.size(), 2); 33 | { 34 | auto& nodeData = testNodes.at(0).asItem(); 35 | ASSERT_EQ(nodeData.paramsCount(), 1); 36 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 37 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 38 | } 39 | { 40 | auto& nodeData = testNodes.at(1).asItem(); 41 | ASSERT_EQ(nodeData.paramsCount(), 1); 42 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 43 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 44 | } 45 | } 46 | 47 | TEST(TestNodeListParser, BasicCR) 48 | { 49 | auto result = parse( 50 | "testStr = Hello\r" 51 | "#testNodes:\r" 52 | "###\r" 53 | " testInt = 3\r" 54 | "###\r" 55 | " testInt = 2\r" 56 | ); 57 | 58 | auto& tree = result.root().asItem(); 59 | ASSERT_EQ(tree.paramsCount(), 1); 60 | ASSERT_EQ(tree.hasParam("testStr"), 1); 61 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 62 | ASSERT_EQ(tree.nodesCount(), 1); 63 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 64 | auto& testNodes = tree.node("testNodes").asList(); 65 | ASSERT_EQ(testNodes.size(), 2); 66 | { 67 | auto& nodeData = testNodes.at(0).asItem(); 68 | ASSERT_EQ(nodeData.paramsCount(), 1); 69 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 70 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 71 | } 72 | { 73 | auto& nodeData = testNodes.at(1).asItem(); 74 | ASSERT_EQ(nodeData.paramsCount(), 1); 75 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 76 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 77 | } 78 | } 79 | 80 | TEST(TestNodeListParser, BasicCRLF) 81 | { 82 | auto result = parse( 83 | "testStr = Hello\r\n" 84 | "#testNodes:\r\n" 85 | "###\r\n" 86 | " testInt = 3\r\n" 87 | "###\r\n" 88 | " testInt = 2\r\n" 89 | ); 90 | 91 | auto& tree = result.root().asItem(); 92 | ASSERT_EQ(tree.paramsCount(), 1); 93 | ASSERT_EQ(tree.hasParam("testStr"), 1); 94 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 95 | ASSERT_EQ(tree.nodesCount(), 1); 96 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 97 | auto& testNodes = tree.node("testNodes").asList(); 98 | ASSERT_EQ(testNodes.size(), 2); 99 | { 100 | auto& nodeData = testNodes.at(0).asItem(); 101 | ASSERT_EQ(nodeData.paramsCount(), 1); 102 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 103 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 104 | } 105 | { 106 | auto& nodeData = testNodes.at(1).asItem(); 107 | ASSERT_EQ(nodeData.paramsCount(), 1); 108 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 109 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 110 | } 111 | } 112 | 113 | 114 | TEST(TestNodeListParser, BasicClosedManually) 115 | { 116 | auto result = parse(R"( 117 | #testNodes: 118 | ### 119 | testInt = 3 120 | ### 121 | testInt = 2 122 | - 123 | testStr = Hello 124 | )"); 125 | 126 | auto& tree = result.root().asItem(); 127 | ASSERT_EQ(tree.paramsCount(), 1); 128 | ASSERT_EQ(tree.hasParam("testStr"), 1); 129 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 130 | ASSERT_EQ(tree.nodesCount(), 1); 131 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 132 | auto& testNodes = tree.node("testNodes").asList(); 133 | ASSERT_EQ(testNodes.size(), 2); 134 | { 135 | auto& nodeData = testNodes.at(0).asItem(); 136 | ASSERT_EQ(nodeData.paramsCount(), 1); 137 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 138 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 139 | } 140 | { 141 | auto& nodeData = testNodes.at(1).asItem(); 142 | ASSERT_EQ(nodeData.paramsCount(), 1); 143 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 144 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 145 | } 146 | } 147 | 148 | TEST(TestNodeListParser, BasicClosedByName) 149 | { 150 | 151 | auto result = parse(R"( 152 | #testNodes: 153 | ### 154 | testInt = 3 155 | ### 156 | testInt = 2 157 | --testNodes 158 | testStr = Hello 159 | )"); 160 | 161 | auto& tree = result.root().asItem(); 162 | ASSERT_EQ(tree.paramsCount(), 1); 163 | ASSERT_EQ(tree.hasParam("testStr"), 1); 164 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 165 | ASSERT_EQ(tree.nodesCount(), 1); 166 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 167 | auto& testNodes = tree.node("testNodes").asList(); 168 | ASSERT_EQ(testNodes.size(), 2); 169 | { 170 | auto& nodeData = testNodes.at(0).asItem(); 171 | ASSERT_EQ(nodeData.paramsCount(), 1); 172 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 173 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 174 | } 175 | { 176 | auto& nodeData = testNodes.at(1).asItem(); 177 | ASSERT_EQ(nodeData.paramsCount(), 1); 178 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 179 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 180 | } 181 | } 182 | 183 | TEST(TestNodeListParser, NestedCloseToRoot) 184 | { 185 | auto result = parse(R"( 186 | #testCfg: 187 | testStr = Hello 188 | #testNodes: 189 | ### 190 | testInt = 3 191 | ### 192 | testInt = 2 193 | --- 194 | testDouble = 0.5 195 | )"); 196 | 197 | auto& tree = result.root().asItem(); 198 | ASSERT_EQ(tree.paramsCount(), 1); 199 | ASSERT_EQ(tree.hasParam("testDouble"), 1); 200 | EXPECT_EQ(tree.param("testDouble").value(), "0.5"); 201 | ASSERT_EQ(tree.nodesCount(), 1); 202 | ASSERT_EQ(tree.hasNode("testCfg"), 1); 203 | auto& testCfg = tree.node("testCfg").asItem(); 204 | ASSERT_EQ(testCfg.paramsCount(), 1); 205 | ASSERT_EQ(testCfg.hasParam("testStr"), 1); 206 | EXPECT_EQ(testCfg.param("testStr").value(), "Hello"); 207 | ASSERT_EQ(testCfg.nodesCount(), 1); 208 | ASSERT_EQ(testCfg.hasNode("testNodes"), 1); 209 | auto& testNodes = testCfg.node("testNodes").asList(); 210 | ASSERT_EQ(testNodes.size(), 2); 211 | { 212 | auto& nodeData = testNodes.at(0).asItem(); 213 | ASSERT_EQ(nodeData.paramsCount(), 1); 214 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 215 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 216 | } 217 | { 218 | auto& nodeData = testNodes.at(1).asItem(); 219 | ASSERT_EQ(nodeData.paramsCount(), 1); 220 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 221 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 222 | } 223 | } 224 | 225 | TEST(TestNodeListParser, NestedClosedByName) 226 | { 227 | auto result = parse(R"( 228 | #testCfg: 229 | testStr = Hello 230 | #testNodes: 231 | ### 232 | testInt = 3 233 | ### 234 | testInt = 2 235 | --testCfg 236 | testDouble = 0.5 237 | )"); 238 | 239 | auto& tree = result.root().asItem(); 240 | ASSERT_EQ(tree.paramsCount(), 1); 241 | ASSERT_EQ(tree.hasParam("testDouble"), 1); 242 | EXPECT_EQ(tree.param("testDouble").value(), "0.5"); 243 | ASSERT_EQ(tree.nodesCount(), 1); 244 | ASSERT_EQ(tree.hasNode("testCfg"), 1); 245 | auto& testCfg = tree.node("testCfg").asItem(); 246 | ASSERT_EQ(testCfg.paramsCount(), 1); 247 | ASSERT_EQ(testCfg.hasParam("testStr"), 1); 248 | EXPECT_EQ(testCfg.param("testStr").value(), "Hello"); 249 | ASSERT_EQ(testCfg.nodesCount(), 1); 250 | ASSERT_EQ(testCfg.hasNode("testNodes"), 1); 251 | auto& testNodes = testCfg.node("testNodes").asList(); 252 | ASSERT_EQ(testNodes.size(), 2); 253 | { 254 | auto& nodeData = testNodes.at(0).asItem(); 255 | ASSERT_EQ(nodeData.paramsCount(), 1); 256 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 257 | EXPECT_EQ(nodeData.param("testInt").value(), "3"); 258 | } 259 | { 260 | auto& nodeData = testNodes.at(1).asItem(); 261 | ASSERT_EQ(nodeData.paramsCount(), 1); 262 | ASSERT_EQ(nodeData.hasParam("testInt"), 1); 263 | EXPECT_EQ(nodeData.param("testInt").value(), "2"); 264 | } 265 | } 266 | 267 | TEST(TestNodeListParser, NestedCfgList) 268 | { 269 | auto result = parse(R"( 270 | #testList: 271 | ### 272 | testStr = Hello 273 | #testNodes: 274 | ### 275 | testInt = 3 276 | ### 277 | testInt = 33 278 | - 279 | ### 280 | #testNodes: 281 | ### 282 | testInt = 5 283 | - 284 | testStr = World 285 | - 286 | testStr = Hello 287 | )"); 288 | 289 | auto& tree = result.root().asItem(); 290 | ASSERT_EQ(tree.paramsCount(), 1); 291 | ASSERT_EQ(tree.hasParam("testStr"), 1); 292 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 293 | ASSERT_EQ(tree.nodesCount(), 1); 294 | ASSERT_EQ(tree.hasNode("testList"), 1); 295 | auto& testList = tree.node("testList").asList(); 296 | ASSERT_EQ(testList.size(), 2); 297 | { 298 | auto& nodeData = testList.at(0).asItem(); 299 | ASSERT_EQ(nodeData.paramsCount(), 1); 300 | ASSERT_EQ(nodeData.hasParam("testStr"), 1); 301 | EXPECT_EQ(nodeData.param("testStr").value(), "Hello"); 302 | ASSERT_EQ(nodeData.nodesCount(), 1); 303 | ASSERT_EQ(nodeData.hasNode("testNodes"), 1); 304 | auto& testNodes = nodeData.node("testNodes").asList(); 305 | ASSERT_EQ(testNodes.size(), 2); 306 | { 307 | auto& childNodeData = testNodes.at(0).asItem(); 308 | ASSERT_EQ(childNodeData.paramsCount(), 1); 309 | ASSERT_EQ(childNodeData.hasParam("testInt"), 1); 310 | EXPECT_EQ(childNodeData.param("testInt").value(), "3"); 311 | } 312 | { 313 | auto& childNodeData = testNodes.at(1).asItem(); 314 | ASSERT_EQ(childNodeData.paramsCount(), 1); 315 | ASSERT_EQ(childNodeData.hasParam("testInt"), 1); 316 | EXPECT_EQ(childNodeData.param("testInt").value(), "33"); 317 | } 318 | } 319 | { 320 | auto& nodeData = testList.at(1).asItem(); 321 | ASSERT_EQ(nodeData.paramsCount(), 1); 322 | ASSERT_EQ(nodeData.hasParam("testStr"), 1); 323 | EXPECT_EQ(nodeData.param("testStr").value(), "World"); 324 | ASSERT_EQ(nodeData.nodesCount(), 1); 325 | ASSERT_EQ(nodeData.hasNode("testNodes"), 1); 326 | auto& testNodes = nodeData.node("testNodes").asList(); 327 | ASSERT_EQ(testNodes.size(), 1); 328 | { 329 | auto& childNodeData = testNodes.at(0).asItem(); 330 | ASSERT_EQ(childNodeData.paramsCount(), 1); 331 | ASSERT_EQ(childNodeData.hasParam("testInt"), 1); 332 | EXPECT_EQ(childNodeData.param("testInt").value(), "5"); 333 | } 334 | } 335 | } 336 | 337 | TEST(TestNodeListParser, NestedCfg2List) 338 | { 339 | auto result = parse(R"( 340 | #testList: 341 | ### 342 | testDouble = 3.0 343 | #testNode: 344 | testInt = 3 345 | ### 346 | testDouble = 5.0 347 | #testNode: 348 | testInt = 5 349 | --testList 350 | testStr = Hello 351 | )"); 352 | 353 | auto& tree = result.root().asItem(); 354 | ASSERT_EQ(tree.paramsCount(), 1); 355 | ASSERT_EQ(tree.hasParam("testStr"), 1); 356 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 357 | ASSERT_EQ(tree.nodesCount(), 1); 358 | ASSERT_EQ(tree.hasNode("testList"), 1); 359 | auto& testList = tree.node("testList").asList(); 360 | ASSERT_EQ(testList.size(), 2); 361 | { 362 | auto& nodeData = testList.at(0).asItem(); 363 | ASSERT_EQ(nodeData.paramsCount(), 1); 364 | ASSERT_EQ(nodeData.hasParam("testDouble"), 1); 365 | EXPECT_EQ(nodeData.param("testDouble").value(), "3.0"); 366 | ASSERT_EQ(nodeData.nodesCount(), 1); 367 | ASSERT_EQ(nodeData.hasNode("testNode"), 1); 368 | auto& testNode = nodeData.node("testNode").asItem(); 369 | ASSERT_EQ(testNode.paramsCount(), 1); 370 | ASSERT_EQ(testNode.hasParam("testInt"), 1); 371 | EXPECT_EQ(testNode.param("testInt").value(), "3"); 372 | } 373 | { 374 | auto& nodeData = testList.at(1).asItem(); 375 | ASSERT_EQ(nodeData.paramsCount(), 1); 376 | ASSERT_EQ(nodeData.hasParam("testDouble"), 1); 377 | EXPECT_EQ(nodeData.param("testDouble").value(), "5.0"); 378 | ASSERT_EQ(nodeData.nodesCount(), 1); 379 | ASSERT_EQ(nodeData.hasNode("testNode"), 1); 380 | auto& testNode = nodeData.node("testNode").asItem(); 381 | ASSERT_EQ(testNode.paramsCount(), 1); 382 | ASSERT_EQ(testNode.hasParam("testInt"), 1); 383 | EXPECT_EQ(testNode.param("testInt").value(), "5"); 384 | } 385 | } 386 | 387 | TEST(TestNodeListParser, EmptyNodeList) 388 | { 389 | auto result = parse(R"( 390 | testStr = Hello 391 | #testNodes: 392 | ### 393 | --- 394 | )"); 395 | 396 | auto& tree = result.root().asItem(); 397 | ASSERT_EQ(tree.paramsCount(), 1); 398 | ASSERT_EQ(tree.hasParam("testStr"), 1); 399 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 400 | ASSERT_EQ(tree.nodesCount(), 1); 401 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 402 | auto& testNodes = tree.node("testNodes").asList(); 403 | ASSERT_EQ(testNodes.size(), 0); 404 | } 405 | 406 | TEST(TestNodeListParser, EmptyNodeList2) 407 | { 408 | auto result = parse(R"( 409 | testStr = Hello 410 | #testNodes: 411 | ### 412 | )"); 413 | 414 | auto& tree = result.root().asItem(); 415 | ASSERT_EQ(tree.paramsCount(), 1); 416 | ASSERT_EQ(tree.hasParam("testStr"), 1); 417 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 418 | ASSERT_EQ(tree.nodesCount(), 1); 419 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 420 | auto& testNodes = tree.node("testNodes").asList(); 421 | ASSERT_EQ(testNodes.size(), 0); 422 | } 423 | 424 | TEST(TestNodeListParser, EmptyNodeList3) 425 | { 426 | auto result = parse(R"( 427 | testStr = Hello 428 | #testNodes: 429 | ###)"); 430 | 431 | auto& tree = result.root().asItem(); 432 | ASSERT_EQ(tree.paramsCount(), 1); 433 | ASSERT_EQ(tree.hasParam("testStr"), 1); 434 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 435 | ASSERT_EQ(tree.nodesCount(), 1); 436 | ASSERT_EQ(tree.hasNode("testNodes"), 1); 437 | auto& testNodes = tree.node("testNodes").asList(); 438 | ASSERT_EQ(testNodes.size(), 0); 439 | } 440 | 441 | TEST(TestNodeListParser, NestedEmptyCloseToRoot) 442 | { 443 | auto result = parse(R"( 444 | #testCfg: 445 | testStr = Hello 446 | #testNodes: 447 | ### 448 | --- 449 | testDouble = 0.5 450 | )"); 451 | 452 | auto& tree = result.root().asItem(); 453 | ASSERT_EQ(tree.paramsCount(), 1); 454 | ASSERT_EQ(tree.hasParam("testDouble"), 1); 455 | EXPECT_EQ(tree.param("testDouble").value(), "0.5"); 456 | ASSERT_EQ(tree.nodesCount(), 1); 457 | ASSERT_EQ(tree.hasNode("testCfg"), 1); 458 | auto& testCfg = tree.node("testCfg").asItem(); 459 | ASSERT_EQ(testCfg.paramsCount(), 1); 460 | ASSERT_EQ(testCfg.hasParam("testStr"), 1); 461 | EXPECT_EQ(testCfg.param("testStr").value(), "Hello"); 462 | ASSERT_EQ(testCfg.nodesCount(), 1); 463 | ASSERT_EQ(testCfg.hasNode("testNodes"), 1); 464 | auto& testNodes = testCfg.node("testNodes").asList(); 465 | ASSERT_EQ(testNodes.size(), 0); 466 | } 467 | 468 | TEST(TestNodeListParser, NestedEmptyClosedByName) 469 | { 470 | auto result = parse(R"( 471 | #testCfg: 472 | testStr = Hello 473 | #testNodes: 474 | ### 475 | --testCfg 476 | testDouble = 0.5 477 | )"); 478 | 479 | auto& tree = result.root().asItem(); 480 | ASSERT_EQ(tree.paramsCount(), 1); 481 | ASSERT_EQ(tree.hasParam("testDouble"), 1); 482 | EXPECT_EQ(tree.param("testDouble").value(), "0.5"); 483 | ASSERT_EQ(tree.nodesCount(), 1); 484 | ASSERT_EQ(tree.hasNode("testCfg"), 1); 485 | auto& testCfg = tree.node("testCfg").asItem(); 486 | ASSERT_EQ(testCfg.paramsCount(), 1); 487 | ASSERT_EQ(testCfg.hasParam("testStr"), 1); 488 | EXPECT_EQ(testCfg.param("testStr").value(), "Hello"); 489 | ASSERT_EQ(testCfg.nodesCount(), 1); 490 | ASSERT_EQ(testCfg.hasNode("testNodes"), 1); 491 | auto& testNodes = testCfg.node("testNodes").asList(); 492 | ASSERT_EQ(testNodes.size(), 0); 493 | } 494 | 495 | TEST(TestNodeListParser, NestedEmptyCfgList) 496 | { 497 | auto result = parse(R"( 498 | #testList: 499 | ### 500 | testStr = Hello 501 | #testNodes: 502 | ### 503 | - 504 | ### 505 | #testNodes: 506 | ### 507 | testInt = 5 508 | - 509 | testStr = World 510 | - 511 | testStr = Hello 512 | )"); 513 | 514 | auto& tree = result.root().asItem(); 515 | ASSERT_EQ(tree.paramsCount(), 1); 516 | ASSERT_EQ(tree.hasParam("testStr"), 1); 517 | EXPECT_EQ(tree.param("testStr").value(), "Hello"); 518 | ASSERT_EQ(tree.nodesCount(), 1); 519 | ASSERT_EQ(tree.hasNode("testList"), 1); 520 | auto& testList = tree.node("testList").asList(); 521 | ASSERT_EQ(testList.size(), 2); 522 | { 523 | auto& nodeData = testList.at(0).asItem(); 524 | ASSERT_EQ(nodeData.paramsCount(), 1); 525 | ASSERT_EQ(nodeData.hasParam("testStr"), 1); 526 | EXPECT_EQ(nodeData.param("testStr").value(), "Hello"); 527 | ASSERT_EQ(nodeData.nodesCount(), 1); 528 | ASSERT_EQ(nodeData.hasNode("testNodes"), 1); 529 | auto& testNodes = nodeData.node("testNodes").asList(); 530 | ASSERT_EQ(testNodes.size(), 0); 531 | } 532 | { 533 | auto& nodeData = testList.at(1).asItem(); 534 | ASSERT_EQ(nodeData.paramsCount(), 1); 535 | ASSERT_EQ(nodeData.hasParam("testStr"), 1); 536 | EXPECT_EQ(nodeData.param("testStr").value(), "World"); 537 | ASSERT_EQ(nodeData.nodesCount(), 1); 538 | ASSERT_EQ(nodeData.hasNode("testNodes"), 1); 539 | auto& testNodes = nodeData.node("testNodes").asList(); 540 | ASSERT_EQ(testNodes.size(), 1); 541 | { 542 | auto& childNodeData = testNodes.at(0).asItem(); 543 | ASSERT_EQ(childNodeData.paramsCount(), 1); 544 | ASSERT_EQ(childNodeData.hasParam("testInt"), 1); 545 | EXPECT_EQ(childNodeData.param("testInt").value(), "5"); 546 | } 547 | } 548 | } 549 | 550 | TEST(TestNodeListParser, InvalidListSeparatorLineError) 551 | { 552 | assert_exception( 553 | [&] 554 | { 555 | parse(R"( 556 | #testNodes: 557 | ### error 558 | testInt = 3 559 | ### 560 | testInt = 2 561 | - 562 | testStr = Hello 563 | )"); 564 | }, 565 | [](const figcone::ConfigError& error) 566 | { 567 | EXPECT_EQ( 568 | std::string{error.what()}, 569 | "[line:3, column:13] Wrong config node list 'testNodes' format: " 570 | "there can't be anything besides comments and whitespaces " 571 | "on the same line with list separator '###'"); 572 | }); 573 | } 574 | 575 | TEST(TestNodeListParser, InvalidListSeparatorError) 576 | { 577 | assert_exception( 578 | [&] 579 | { 580 | parse(R"( 581 | #testNodes: 582 | ##error 583 | testInt = 3 584 | ### 585 | testInt = 2 586 | - 587 | testStr = Hello 588 | )"); 589 | }, 590 | [](const figcone::ConfigError& error) 591 | { 592 | EXPECT_EQ(std::string{error.what()}, "[line:3, column:16] Config node can't have a multiline name"); 593 | }); 594 | } 595 | 596 | TEST(TestNodeListParser, InvalidListError) 597 | { 598 | assert_exception( 599 | [&] 600 | { 601 | parse(R"( 602 | #testNodes: 603 | error 604 | ### 605 | testInt = 3 606 | ### 607 | testInt = 2 608 | - 609 | testStr = Hello 610 | )"); 611 | }, 612 | [](const figcone::ConfigError& error) 613 | { 614 | EXPECT_EQ( 615 | std::string{error.what()}, 616 | "[line:3, column:14] Wrong param 'error' format: parameter's value must be placed on the same " 617 | "line as its name"); 618 | }); 619 | } 620 | 621 | } //namespace test_nodelistparser 622 | --------------------------------------------------------------------------------