Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Testing/
.cproject
.settings
.vscode
.cache
74 changes: 42 additions & 32 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
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")
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

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
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall;-Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/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()
73 changes: 73 additions & 0 deletions test/test_inifile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>() == 16);
REQUIRE(inif["Foo"]["hex_upper"].as<int>() == 42);
REQUIRE(inif["Foo"]["hex_neg"].as<int>() == -16);
}

SECTION("decimal numbers")
{
REQUIRE(inif["Foo"]["dec"].as<int>() == 42);
REQUIRE(inif["Foo"]["dec_leading_zero"].as<int>() == 10);
REQUIRE(inif["Foo"]["dec_neg"].as<int>() == -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<unsigned>() == 255);
REQUIRE(inif["Foo"]["udec"].as<unsigned>() == 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<int>(), 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<std::string>() == "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);
}