From 2e9ac690394f80ab9e21aaa0ca01f494084dc65e Mon Sep 17 00:00:00 2001 From: Master92 Date: Thu, 14 Nov 2024 10:35:05 +0100 Subject: [PATCH 1/4] Add C interface implementation --- include/cppIni/cppIni_c.h | 44 ++++++++++++++++++++ src/CInterface.cpp | 84 +++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 2 + 3 files changed, 130 insertions(+) create mode 100644 include/cppIni/cppIni_c.h create mode 100644 src/CInterface.cpp diff --git a/include/cppIni/cppIni_c.h b/include/cppIni/cppIni_c.h new file mode 100644 index 0000000..dbe9a82 --- /dev/null +++ b/include/cppIni/cppIni_c.h @@ -0,0 +1,44 @@ +// cppIni - A C++20 library for reading and writing INI files +// Copyright (C) 2023-2024 Nils Hofmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* pFile; +/// \file cppIni_c.h +/// \brief The C API for cppIni +/// +/// \details +/// This file contains the C API for cppIni. It is a very thin wrapper around +/// the C++ API. It is intended for use with languages that do not support +/// C++. + +CPPINI_EXPORT pFile cppIni_open(const char* filename); ///< Opens a file +CPPINI_EXPORT void cppIni_close(pFile* file); ///< Closes a file +CPPINI_EXPORT void cppIni_set(pFile file, const char* section, const char* key, const char* value); ///< Sets a value +CPPINI_EXPORT const char* cppIni_gets(pFile file, const char* section, const char* key, char* out, size_t outSize); ///< Gets a string +CPPINI_EXPORT int cppIni_geti(pFile file, const char* section, const char* key); ///< Gets an integer +CPPINI_EXPORT float cppIni_getf(pFile file, const char* section, const char* key); ///< Gets a float + +#ifdef __cplusplus +} +#endif diff --git a/src/CInterface.cpp b/src/CInterface.cpp new file mode 100644 index 0000000..8d6db42 --- /dev/null +++ b/src/CInterface.cpp @@ -0,0 +1,84 @@ +// cppIni - A C++20 library for reading and writing INI files +// Copyright (C) 2023-2024 Nils Hofmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include +#include + +/// Opens a file for reading and writing. Throws an exception if the file cannot +/// be opened. +/// +/// \param[in] filename The name of the file to open +/// \return A pointer to a File object +pFile cppIni_open(const char* filename) +{ + return new File(filename); +} + +/// Closes a file that was opened with cppIni_open(). +/// \param[in] file A pointer to a File object +void cppIni_close(pFile* file) +{ + delete static_cast(*file); +} + +/// \param[in] file A pointer to a File object +/// \param[in] section The name of the section to add +/// \param[in] key The name of the key to add +/// \param[in] value The value to add +void cppIni_set(pFile file, const char* section, const char* key, const char* value) +{ + static_cast(file)->set(section, key, value); +} + +/// \param[in] file A pointer to a File object +/// \param[in] section The name of the section to get +/// \param[in] key The name of the key to get +/// \param[out] out A buffer to store the value in +/// \param[in] outSize The size of the buffer +/// \return A pointer to the buffer +const char* cppIni_gets(pFile file, const char* section, const char* key, char* out, size_t outSize) +{ + const auto value = static_cast(file)->get(section, key); + + if (value.empty()) { + return out; + } + + std::strncpy(out, value.data(), outSize); + return out; +} + +/// \see cppIni_gets +/// \param[in] file A pointer to a File object +/// \param[in] section The name of the section to get +/// \param[in] key The name of the key to get +/// \return The value of the key +int cppIni_geti(pFile file, const char* section, const char* key) +{ + return static_cast(file)->get(section, key); +} + +/// \see cppIni_gets +/// \param[in] file A pointer to a File object +/// \param[in] section The name of the section to get +/// \param[in] key The name of the key to get +/// \return The value of the key +float cppIni_getf(pFile file, const char* section, const char* key) +{ + return static_cast(file)->get(section, key); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a6c813e..eecc4e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.24) set(CMAKE_DEBUG_POSTFIX d) set(SOURCES + CInterface.cpp Entry.cpp File.cpp Section.cpp @@ -10,6 +11,7 @@ set(SOURCES set(API_HEADERS cppIni.h + cppIni_c.h Entry.h File.h Section.h From 7bf0ac78e7a3d137f2737695f7cf71bcde1bb48c Mon Sep 17 00:00:00 2001 From: Master92 Date: Thu, 14 Nov 2024 10:35:30 +0100 Subject: [PATCH 2/4] Add unit tests for C interface API --- tests/CInterfaceTest.cpp | 69 ++++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + 2 files changed, 70 insertions(+) create mode 100644 tests/CInterfaceTest.cpp diff --git a/tests/CInterfaceTest.cpp b/tests/CInterfaceTest.cpp new file mode 100644 index 0000000..b7a6502 --- /dev/null +++ b/tests/CInterfaceTest.cpp @@ -0,0 +1,69 @@ +// cppIni - A C++20 library for reading and writing INI files +// Copyright (C) 2023-2024 Nils Hofmann +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +#include +#include + +static const std::string fileName = std::format("{}{}", WORKING_DIR, "/res/test.ini");; + +TEST_SUITE_BEGIN("CInterface"); + +TEST_CASE("Construction of File object") +{ + void* file; + CHECK_NOTHROW(file = cppIni_open(fileName.c_str())); + CHECK_NE(file, nullptr); + CHECK_NOTHROW(cppIni_close(&file)); +} + +struct ScopeGuard +{ + ScopeGuard(pFile file) : file(file){}; + ~ScopeGuard(){ cppIni_close(&file); }; + pFile file; +}; + +TEST_CASE("Read a string entry") +{ + void* file = cppIni_open(fileName.c_str()); + ScopeGuard guard{file}; + + std::array buffer{0}; + cppIni_gets(file, "Section1", "Entry1", buffer.data(), buffer.size()); + + CHECK_EQ(std::string_view{buffer.data()}, "Value1"); +} + +TEST_CASE("Read an integer entry") +{ + void* file = cppIni_open(fileName.c_str()); + ScopeGuard guard{file}; + + CHECK_EQ(cppIni_geti(file, "Section1", "IntEntry"), 42); +} + +TEST_CASE("Read a floating point value entry") +{ + void* file = cppIni_open(fileName.c_str()); + ScopeGuard guard{file}; + + CHECK_LT(std::abs(cppIni_getf(file, "Section1.Subsection1", "DoubleEntry") - 3.1415), 0.001); +} + +TEST_SUITE_END(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c78aa1..f086d2d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,7 @@ set(TEST_SOURCES EntryTest.cpp FileTest.cpp SectionTest.cpp + CInterfaceTest.cpp ) add_executable(${PROJECT_NAME}_tests ${TEST_SOURCES}) From 92868dee6edab6532e063184b3bce36b99fed16f Mon Sep 17 00:00:00 2001 From: Master92 Date: Thu, 14 Nov 2024 12:46:59 +0100 Subject: [PATCH 3/4] Add test case for reading a non-existing entry --- tests/CInterfaceTest.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/CInterfaceTest.cpp b/tests/CInterfaceTest.cpp index b7a6502..729d777 100644 --- a/tests/CInterfaceTest.cpp +++ b/tests/CInterfaceTest.cpp @@ -50,6 +50,16 @@ TEST_CASE("Read a string entry") CHECK_EQ(std::string_view{buffer.data()}, "Value1"); } +TEST_CASE("Try to read a non-existing entry") +{ + void* file = cppIni_open(fileName.c_str()); + ScopeGuard guard{file}; + + std::array buffer{0}; + CHECK_EQ(cppIni_gets(file, "Section1", "NonExistingEntry", buffer.data(), buffer.size()), buffer.data()); + CHECK_EQ(buffer[0], '\0'); +} + TEST_CASE("Read an integer entry") { void* file = cppIni_open(fileName.c_str()); From a6f049fca0a0668a51c87c68de770b76444a1147 Mon Sep 17 00:00:00 2001 From: Master92 Date: Thu, 14 Nov 2024 12:47:12 +0100 Subject: [PATCH 4/4] Add value setter test case --- tests/CInterfaceTest.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/CInterfaceTest.cpp b/tests/CInterfaceTest.cpp index 729d777..a979e59 100644 --- a/tests/CInterfaceTest.cpp +++ b/tests/CInterfaceTest.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . #include +#include #include #include @@ -60,6 +61,25 @@ TEST_CASE("Try to read a non-existing entry") CHECK_EQ(buffer[0], '\0'); } +TEST_CASE("Change a value") +{ + constexpr auto tempFileName = "tmp.ini"; + std::filesystem::copy_file(fileName, tempFileName); + + { + constexpr auto newValue = 1337; + void* file = cppIni_open(tempFileName); + ScopeGuard guard{file}; + + const auto previousValue = cppIni_geti(file, "Section1", "IntEntry"); + CHECK_NE(previousValue, newValue); + cppIni_set(file, "Section1", "IntEntry", std::to_string(newValue).c_str()); + CHECK_EQ(cppIni_geti(file, "Section1", "IntEntry"), newValue); + } + + std::filesystem::remove(tempFileName); +} + TEST_CASE("Read an integer entry") { void* file = cppIni_open(fileName.c_str());