6 Commits
v1.2.0 ... main

12 changed files with 242 additions and 81 deletions

View File

@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(mkdir:*)",
"Bash(cmake:*)",
"Bash(make:*)",
"Bash(cat:*)"
],
"deny": []
}
}

1
.gitignore vendored
View File

@@ -38,3 +38,4 @@ cmake-build*/**
.idea .idea
puml/** puml/**
.vscode/settings.json .vscode/settings.json
CMakeUserPresets.json

View File

@@ -10,6 +10,8 @@
#include <cctype> // std::isdigit #include <cctype> // std::isdigit
#include <algorithm> // std::all_of std::transform #include <algorithm> // std::all_of std::transform
#include <filesystem> // For file size checking #include <filesystem> // For file size checking
#include "arffFiles_config.h"
// Summary information structure for ARFF files // Summary information structure for ARFF files
struct ArffSummary { struct ArffSummary {
@@ -63,8 +65,7 @@ struct ArffSummary {
* // Thread 2: globalArff.load("file2.arff"); // UNSAFE! * // Thread 2: globalArff.load("file2.arff"); // UNSAFE!
*/ */
class ArffFiles { class ArffFiles {
const std::string VERSION = "1.1.0"; private:
// Memory usage limits (configurable via environment variables) // Memory usage limits (configurable via environment variables)
static constexpr size_t DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB static constexpr size_t DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
static constexpr size_t DEFAULT_MAX_SAMPLES = 1000000; // 1 million samples static constexpr size_t DEFAULT_MAX_SAMPLES = 1000000; // 1 million samples
@@ -103,11 +104,11 @@ public:
return *this; return *this;
} }
// Copy constructor (explicitly defaulted) // Copy constructor (explicitly delete)
ArffFiles(const ArffFiles& other) = default; ArffFiles(const ArffFiles& other) = delete;
// Copy assignment operator (explicitly defaulted) // Copy assignment operator (explicitly deleted)
ArffFiles& operator=(const ArffFiles& other) = default; ArffFiles& operator=(const ArffFiles& other) = delete;
// Destructor (explicitly defaulted) // Destructor (explicitly defaulted)
~ArffFiles() = default; ~ArffFiles() = default;
@@ -252,11 +253,12 @@ public:
} }
return result; return result;
} }
std::string version() const { return VERSION; } std::string version() const { return ARFFLIB_VERSION; }
private: private:
// Helper function to validate file path for security // Helper function to validate file path for security
static void validateFilePath(const std::string& fileName) { static void validateFilePath(const std::string& fileName)
{
if (fileName.empty()) { if (fileName.empty()) {
throw std::invalid_argument("File path cannot be empty"); throw std::invalid_argument("File path cannot be empty");
} }
@@ -298,14 +300,16 @@ private:
throw std::invalid_argument("Path traversal detected after normalization: " + normalizedStr); throw std::invalid_argument("Path traversal detected after normalization: " + normalizedStr);
} }
} }
} catch (const std::filesystem::filesystem_error& e) { }
catch (const std::filesystem::filesystem_error& e) {
// If filesystem operations fail, we can still proceed with basic validation // If filesystem operations fail, we can still proceed with basic validation
// This ensures compatibility with systems where filesystem might not be fully available // This ensures compatibility with systems where filesystem might not be fully available
} }
} }
// Helper function to validate resource usage limits // Helper function to validate resource usage limits
static void validateResourceLimits(const std::string& fileName, size_t sampleCount = 0, size_t featureCount = 0) { static void validateResourceLimits(const std::string& fileName, size_t sampleCount = 0, size_t featureCount = 0)
{
// Check file size limit // Check file size limit
try { try {
if (std::filesystem::exists(fileName)) { if (std::filesystem::exists(fileName)) {
@@ -314,7 +318,8 @@ private:
throw std::invalid_argument("File size (" + std::to_string(fileSize) + " bytes) exceeds maximum allowed size (" + std::to_string(DEFAULT_MAX_FILE_SIZE) + " bytes)"); throw std::invalid_argument("File size (" + std::to_string(fileSize) + " bytes) exceeds maximum allowed size (" + std::to_string(DEFAULT_MAX_FILE_SIZE) + " bytes)");
} }
} }
} catch (const std::filesystem::filesystem_error&) { }
catch (const std::filesystem::filesystem_error&) {
// If filesystem operations fail, continue without size checking // If filesystem operations fail, continue without size checking
// This ensures compatibility with systems where filesystem might not be available // This ensures compatibility with systems where filesystem might not be available
} }
@@ -622,11 +627,12 @@ private:
// Common helper function to parse ARFF file attributes and count samples // Common helper function to parse ARFF file attributes and count samples
static int parseArffFile(const std::string& fileName, static int parseArffFile(const std::string& fileName,
std::vector<std::pair<std::string, std::string>>& attributes, std::vector<std::pair<std::string, std::string>>& attributes,
std::set<std::string>& uniqueClasses, std::set<std::string>& uniqueClasses,
size_t& sampleCount, size_t& sampleCount,
int classIndex = -1, int classIndex = -1,
const std::string& classNameToFind = "") { const std::string& classNameToFind = "")
{
// Validate file path for security // Validate file path for security
validateFilePath(fileName); validateFilePath(fileName);

View File

@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.1] 2025-07-15 Bug Fixes and Improvements
### Added
- Library version from CMake projecto to `ArffFiles.hpp`
- Library `catch2` as a conan test requirement
- Install target for CMake
## [1.2.0] 2025-06-27 Refactoring and Improvements ## [1.2.0] 2025-06-27 Refactoring and Improvements
### Added ### Added

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(ArffFiles project(ArffFiles
VERSION 1.2.0 VERSION 1.2.1
DESCRIPTION "Library to read Arff Files and return STL vectors with the data read." DESCRIPTION "Library to read Arff Files and return STL vectors with the data read."
HOMEPAGE_URL "https://github.com/rmontanana/ArffFiles" HOMEPAGE_URL "https://github.com/rmontanana/ArffFiles"
LANGUAGES CXX LANGUAGES CXX
@@ -41,14 +41,60 @@ add_subdirectory(config)
# ------- # -------
if (ENABLE_TESTING) if (ENABLE_TESTING)
MESSAGE("Testing enabled") MESSAGE("Testing enabled")
Include(FetchContent) find_package(Catch2 REQUIRED)
FetchContent_Declare(Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.3.2
)
FetchContent_MakeAvailable(Catch2)
include(CTest) include(CTest)
add_subdirectory(tests) add_subdirectory(tests)
endif (ENABLE_TESTING) endif (ENABLE_TESTING)
add_library(ArffFiles INTERFACE ArffFiles.hpp) add_library(ArffFiles INTERFACE ArffFiles.hpp)
target_include_directories(ArffFiles INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/configured_files/include>
$<INSTALL_INTERFACE:include>
)
# Install
# -------
install(TARGETS ArffFiles EXPORT ArffFilesTargets
INCLUDES DESTINATION include
)
install(EXPORT ArffFilesTargets
FILE ArffFilesTargets.cmake
NAMESPACE ArffFiles::
DESTINATION lib/cmake/ArffFiles
)
# Install the main header file
install(FILES ArffFiles.hpp
DESTINATION include
)
# Install the generated configuration header
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/configured_files/include/arffFiles_config.h"
DESTINATION include
)
# Install documentation files
install(FILES LICENSE README.md
DESTINATION share/doc/ArffFiles
)
# Create and install package configuration files
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/ArffFilesConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/ArffFilesConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/ArffFilesConfig.cmake"
INSTALL_DESTINATION lib/cmake/ArffFiles
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/ArffFilesConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/ArffFilesConfigVersion.cmake"
DESTINATION lib/cmake/ArffFiles
)

11
CMakeLists_conan.txt Normal file
View File

@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
project(ArffFiles
VERSION 1.2.1
DESCRIPTION "Library to read Arff Files and return STL vectors with the data read."
HOMEPAGE_URL "https://github.com/rmontanana/ArffFiles"
LANGUAGES CXX
)
# Subdirectories
add_subdirectory(config)

View File

@@ -25,10 +25,12 @@ clean: ## Clean the tests info
@echo ">>> Done"; @echo ">>> Done";
build: ## Build a debug version of the project build: ## Build a debug version of the project
@echo ">>> Building Debug ArffFiles..."; @echo ">>> Building Debug Folding...";
@if [ -d ./$(f_debug) ]; then rm -rf ./$(f_debug); fi @if [ -d $(f_debug) ]; then rm -rf $(f_debug); fi
@mkdir $(f_debug); @mkdir $(f_debug);
@cmake -S . -B $(f_debug) -D CMAKE_BUILD_TYPE=Debug -D ENABLE_TESTING=ON -D CODE_COVERAGE=ON conan install . -of $(f_debug) -s build_type=Debug -b missing
cmake -B $(f_debug) -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$(f_debug)/conan_toolchain.cmake -DENABLE_TESTING=ON
cmake --build $(f_debug) -t $(test_targets) $(n_procs)
@echo ">>> Done"; @echo ">>> Done";
opt = "" opt = ""

View File

@@ -29,10 +29,10 @@ A modern C++17 header-only library to read **ARFF (Attribute-Relation File Forma
```bash ```bash
# Add the package to your conanfile.txt # Add the package to your conanfile.txt
[requires] [requires]
arff-files/1.0.1 arff-files/1.2.1
# Or install directly # Or install directly
conan install arff-files/1.0.1@ conan install arff-files/1.2.1@
``` ```
### Manual Installation ### Manual Installation

View File

@@ -0,0 +1,5 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/ArffFilesTargets.cmake")
check_required_components(ArffFiles)

View File

@@ -1,21 +1,29 @@
import re import re
from conan import ConanFile from conan import ConanFile
from conan.tools.files import copy from conan.tools.files import copy
from conan.tools.cmake import CMakeToolchain, CMakeDeps
class ArffFilesConan(ConanFile): class ArffFilesConan(ConanFile):
name = "arff-files" name = "arff-files"
version = "X.X.X" version = "X.X.X"
description = ( description = "Header-only library to read ARFF (Attribute-Relation \
"Header-only library to read ARFF (Attribute-Relation File Format) files and return STL vectors with the data read." File Format) files and return STL vectors with the data read."
)
url = "https://github.com/rmontanana/ArffFiles" url = "https://github.com/rmontanana/ArffFiles"
license = "MIT" license = "MIT"
homepage = "https://github.com/rmontanana/ArffFiles" homepage = "https://github.com/rmontanana/ArffFiles"
topics = ("arff", "data-processing", "file-parsing", "header-only", "cpp17") topics = ("arff", "data-processing", "file-parsing", "header-only", "cpp17")
no_copy_source = True no_copy_source = True
exports_sources = "ArffFiles.hpp", "LICENSE", "README.md" exports_sources = (
"ArffFiles.hpp",
"LICENSE",
"README.md",
"CMakeLists.txt",
"config/*",
"cmake/*",
)
package_type = "header-library" package_type = "header-library"
settings = "build_type", "compiler", "arch", "os"
def init(self): def init(self):
# Read the CMakeLists.txt file to get the version # Read the CMakeLists.txt file to get the version
@@ -28,12 +36,76 @@ class ArffFilesConan(ConanFile):
if match: if match:
self.version = match.group(1) self.version = match.group(1)
def build_requirements(self):
self.tool_requires("cmake/[>=3.15]")
self.test_requires("catch2/3.8.1")
def layout(self):
# Only use cmake_layout for conan packaging, not for development builds
# This can be detected by checking if we're in a conan cache folder
if (
hasattr(self, "folders")
and hasattr(self.folders, "base_build")
and self.folders.base_build
and ".conan2" in self.folders.base_build
):
from conan.tools.cmake import cmake_layout
cmake_layout(self)
def generate(self):
# Generate CMake toolchain file
tc = CMakeToolchain(self)
tc.generate()
# Generate CMake dependencies file (needed for test requirements like catch2)
deps = CMakeDeps(self)
deps.generate()
def build(self):
# Use CMake to generate the config file through existing config system
from conan.tools.cmake import CMake
cmake = CMake(self)
# Configure with minimal options - just enough to generate the config file
cmake.configure(
build_script_folder=None,
cli_args=["-DENABLE_TESTING=OFF", "-DCODE_COVERAGE=OFF"],
)
# No need to build anything, just configure to generate the config file
def package(self): def package(self):
# Copy header file to include directory # Copy header file
copy(self, "*.hpp", src=self.source_folder, dst=self.package_folder, keep_path=False) copy(
self,
"ArffFiles.hpp",
src=self.source_folder,
dst=self.package_folder,
keep_path=False,
)
# Copy the generated config file from CMake build folder
copy(
self,
"arffFiles_config.h",
src=f"{self.build_folder}/configured_files/include",
dst=self.package_folder,
keep_path=False,
)
# Copy license and readme for package documentation # Copy license and readme for package documentation
copy(self, "LICENSE", src=self.source_folder, dst=self.package_folder, keep_path=False) copy(
copy(self, "README.md", src=self.source_folder, dst=self.package_folder, keep_path=False) self,
"LICENSE",
src=self.source_folder,
dst=self.package_folder,
keep_path=False,
)
copy(
self,
"README.md",
src=self.source_folder,
dst=self.package_folder,
keep_path=False,
)
def package_info(self): def package_info(self):
# Header-only library configuration # Header-only library configuration

View File

@@ -1,11 +1,10 @@
#pragma once #pragma once
#include <string> #define ARFFLIB_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#include <string_view> #define ARFFLIB_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define ARFFLIB_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR @ #define ARFFLIB_VERSION "@PROJECT_VERSION@"
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR @
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH @
static constexpr std::string_view arffFiles_project_name = "@PROJECT_NAME@"; static constexpr std::string_view arffFiles_project_name = "@PROJECT_NAME@";
static constexpr std::string_view arffFiles_project_version = "@PROJECT_VERSION@"; static constexpr std::string_view arffFiles_project_version = "@PROJECT_VERSION@";

View File

@@ -3,7 +3,6 @@
#include <catch2/generators/catch_generators.hpp> #include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_string.hpp> #include <catch2/matchers/catch_matchers_string.hpp>
#include "ArffFiles.hpp" #include "ArffFiles.hpp"
#include "arffFiles_config.h"
#include <iostream> #include <iostream>
class Paths { class Paths {
@@ -28,7 +27,7 @@ public:
TEST_CASE("Version Test", "[ArffFiles]") TEST_CASE("Version Test", "[ArffFiles]")
{ {
ArffFiles arff; ArffFiles arff;
REQUIRE(arff.version() == "1.1.0"); REQUIRE(arff.version() == "1.2.1");
} }
TEST_CASE("Load Test", "[ArffFiles]") TEST_CASE("Load Test", "[ArffFiles]")
{ {