diff --git a/.gitignore b/.gitignore index 13ac9b8..460d176 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Testing/ .cproject .settings .vscode +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 72ec0ba..71a40be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,46 +6,56 @@ cmake_minimum_required(VERSION 3.15) -project(inifile-cpp) - -include(CTest) - -set(INICPP_CXX_STANDARD "11" CACHE STRING "C++ standard to use when building tests & examples.") -option(GENERATE_COVERAGE "Enable generating code coverage" OFF) -option(BUILD_TESTS "Enable building unit tests" OFF) -option(BUILD_EXAMPLES "Enable building example applications" OFF) - -set(CMAKE_CXX_STANDARD ${INICPP_CXX_STANDARD}) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -if(CMAKE_COMPILER_IS_GNUCXX) - add_compile_options(-Wall -Wextra) -endif(CMAKE_COMPILER_IS_GNUCXX) - -if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_compile_options(/WX /wd4530) -endif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - - -add_subdirectory(dep) +project(inifile-cpp LANGUAGES CXX) +include(GNUInstallDirs) add_library(inicpp INTERFACE) +add_library(inicpp::inicpp ALIAS inicpp) +target_compile_features(inicpp INTERFACE cxx_std_11) target_include_directories(inicpp INTERFACE $ - $) -add_library(inicpp::inicpp ALIAS inicpp) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/inicpp.h TYPE INCLUDE) - -if(GENERATE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + $) + +if(NOT DEFINED INIFILE_CPP_MASTER_PROJECT) + if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(INIFILE_CPP_MASTER_PROJECT ON) + else() + set(INIFILE_CPP_MASTER_PROJECT OFF) + endif() +endif() + +if(INIFILE_CPP_MASTER_PROJECT) + target_compile_options(inicpp INTERFACE + $<$:-Wall;-Wextra> + $<$:/W4> + ) +endif() + +option(INIFILE_CPP_GENERATE_COVERAGE "Enable generating code coverage" ${INIFILE_CPP_MASTER_PROJECT}) +option(INIFILE_CPP_BUILD_TESTS "Enable building unit tests" ${INIFILE_CPP_MASTER_PROJECT}) +option(INIFILE_CPP_BUILD_EXAMPLES "Enable building example applications" ${INIFILE_CPP_MASTER_PROJECT}) + +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.h" +) + +if(INIFILE_CPP_GENERATE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") # Add required flags (GCC & LLVM/Clang) + message(STATUS "[inicpp] Enabling code coverage generation") target_compile_options(inicpp INTERFACE -O0 -g --coverage) target_link_options(inicpp INTERFACE --coverage) -endif(GENERATE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") +endif() -if(${BUILD_TESTS}) +if(INIFILE_CPP_BUILD_TESTS) + message(STATUS "[inicpp] Building unit tests") + include(CTest) + add_subdirectory(dep) add_subdirectory(test) -endif(${BUILD_TESTS}) +endif() -if(${BUILD_EXAMPLES}) +if(INIFILE_CPP_BUILD_EXAMPLES) + message(STATUS "[inicpp] Building examples") add_subdirectory(examples) -endif(${BUILD_EXAMPLES}) +endif() diff --git a/test/test_inifile.cpp b/test/test_inifile.cpp index f5b2ec6..afa4879 100644 --- a/test/test_inifile.cpp +++ b/test/test_inifile.cpp @@ -903,3 +903,76 @@ TEST_CASE("parse section with duplicate field and overwriteDuplicateFields_ set inif.allowOverwriteDuplicateFields(false); REQUIRE_THROWS(inif.decode("[Foo]\nbar=hello\nbar=world")); } + +// Tests numeric parsing: hex (0x/0X, negative), decimal (with/without leading zeros), unsigned, and invalid conversions +TEST_CASE("numeric parsing", "IniFile") +{ + std::istringstream ss("[Foo]\n" + "hex=0x10\n" + "hex_upper=0X2A\n" + "hex_neg=-0x10\n" + "dec=42\n" + "dec_leading_zero=010\n" + "dec_neg=-15\n"); + ini::IniFile inif(ss); + + SECTION("hex numbers") + { + REQUIRE(inif["Foo"]["hex"].as() == 16); + REQUIRE(inif["Foo"]["hex_upper"].as() == 42); + REQUIRE(inif["Foo"]["hex_neg"].as() == -16); + } + + SECTION("decimal numbers") + { + REQUIRE(inif["Foo"]["dec"].as() == 42); + REQUIRE(inif["Foo"]["dec_leading_zero"].as() == 10); + REQUIRE(inif["Foo"]["dec_neg"].as() == -15); + } +} + +TEST_CASE("parse unsigned numbers", "IniFile") +{ + std::istringstream ss("[Foo]\n" + "uhex=0xFF\n" + "udec=100\n"); + ini::IniFile inif(ss); + + REQUIRE(inif["Foo"]["uhex"].as() == 255); + REQUIRE(inif["Foo"]["udec"].as() == 100); +} + +TEST_CASE("invalid numeric conversion", "IniFile") +{ + std::istringstream ss("[Foo]\n" + "bad=xyz\n"); + ini::IniFile inif(ss); + + REQUIRE_THROWS_AS(inif["Foo"]["bad"].as(), std::invalid_argument); +} + +TEST_CASE("custom escape char", "IniFile") +{ + std::istringstream ss("[Foo]\nbar=hello !#world"); + ini::IniFile inif; + + inif.setEscapeChar('!'); + inif.decode(ss); + + REQUIRE(inif["Foo"]["bar"].as() == "hello #world"); +} + +TEST_CASE("escape char affects comment escaping", "IniFile") +{ + std::istringstream ss("[Foo]\nkey=val\\#notacomment\n"); + ini::IniFile inif(ss); + + inif.setEscapeChar('\\'); + std::string encoded = inif.encode(); + REQUIRE(encoded.find("\\#notacomment") != std::string::npos); + + inif.setEscapeChar('$'); + inif["Foo"]["key"] = "value$#comment"; + encoded = inif.encode(); + REQUIRE(encoded.find("$#comment") != std::string::npos); +} \ No newline at end of file