From dcdb63e2d3e1e09dd60941ae988c1d2377d6d043 Mon Sep 17 00:00:00 2001 From: Joey Kleingers Date: Mon, 12 Feb 2024 12:16:24 -0500 Subject: [PATCH] ENH: ReadCSVFilter and TriangleCentroidFilter improvements (#854) Signed-off-by: Michael Jackson Signed-off-by: Joey Kleingers Co-authored-by: Michael Jackson --- CMakeLists.txt | 1 + .../Algorithms/FeatureFaceCurvature.cpp | 2 + .../Filters/Algorithms/TriangleCentroid.cpp | 6 +- .../SimplnxCore/Filters/ReadCSVFileFilter.cpp | 19 ++- .../Filters/TriangleCentroidFilter.cpp | 2 +- .../SimplnxCore/test/ReadCSVFileTest.cpp | 59 +++----- .../SimplnxCore/test/TriangleCentroidTest.cpp | 2 +- src/simplnx/Utilities/DataArrayUtilities.hpp | 12 +- src/simplnx/Utilities/FileUtilities.cpp | 138 ++++++++++++++++++ src/simplnx/Utilities/FileUtilities.hpp | 94 ++---------- 10 files changed, 198 insertions(+), 137 deletions(-) create mode 100644 src/simplnx/Utilities/FileUtilities.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 14248e6da6..a46dc91888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -688,6 +688,7 @@ set(SIMPLNX_SRCS ${SIMPLNX_SOURCE_DIR}/Utilities/ArrayThreshold.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/FilePathGenerator.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/FilterUtilities.cpp + ${SIMPLNX_SOURCE_DIR}/Utilities/FileUtilities.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/TooltipGenerator.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/TooltipRowItem.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/DataArrayUtilities.cpp diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp index dc4bef877e..211d84be0a 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp @@ -134,6 +134,8 @@ Result<> FeatureFaceCurvature::operator()() g->wait(); // Wait for all the threads to complete before moving on. #endif + // Remove elements containing vertices, because Element neighbors created it quietly under the covers + triangleGeomPtr->deleteElementsContainingVert(); return {}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/TriangleCentroid.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/TriangleCentroid.cpp index 6735812e92..0a668fcf73 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/TriangleCentroid.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/TriangleCentroid.cpp @@ -19,7 +19,7 @@ namespace class CalculateCentroidsImpl { public: - CalculateCentroidsImpl(const TriangleGeom* triangleGeom, Float32Array* centroids, const std::atomic_bool& shouldCancel) + CalculateCentroidsImpl(const TriangleGeom* triangleGeom, Float64Array* centroids, const std::atomic_bool& shouldCancel) : m_TriangleGeom(triangleGeom) , m_Centroids(centroids) , m_ShouldCancel(shouldCancel) @@ -52,7 +52,7 @@ class CalculateCentroidsImpl private: const TriangleGeom* m_TriangleGeom = nullptr; - Float32Array* m_Centroids = nullptr; + Float64Array* m_Centroids = nullptr; const std::atomic_bool& m_ShouldCancel; }; } // namespace @@ -83,7 +83,7 @@ Result<> TriangleCentroid::operator()() const AttributeMatrix& faceAttributeMatrix = triangleGeom->getFaceAttributeMatrixRef(); const DataPath pCentroidsPath = m_InputValues->TriangleGeometryDataPath.createChildPath(faceAttributeMatrix.getName()).createChildPath(m_InputValues->CentroidsArrayName); - auto* centroidsArray = m_DataStructure.getDataAs(pCentroidsPath); + auto* centroidsArray = m_DataStructure.getDataAs(pCentroidsPath); // Parallel algorithm to calculate the centroids ParallelDataAlgorithm dataAlg; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReadCSVFileFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReadCSVFileFilter.cpp index 7054ff6721..8aac5be736 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReadCSVFileFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReadCSVFileFilter.cpp @@ -16,11 +16,10 @@ #include "simplnx/Parameters/DynamicTableParameter.hpp" #include "simplnx/Parameters/ReadCSVFileParameter.hpp" #include "simplnx/Utilities/FileUtilities.hpp" -#include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/StringUtilities.hpp" - #include "simplnx/Utilities/SIMPLConversion.hpp" +#include "simplnx/Utilities/StringUtilities.hpp" +#include #include using namespace nx::core; @@ -309,6 +308,7 @@ IFilter::PreflightResult readHeaders(const std::string& inputFilePath, usize hea headerCache.HeadersLine = headersLineNum; return {}; } + } // namespace namespace nx::core @@ -421,6 +421,11 @@ IFilter::PreflightResult ReadCSVFileFilter::preflightImpl(const DataStructure& d StringVector headers; if(readCSVData.inputFilePath != s_HeaderCache[s_InstanceId].FilePath) { + int64 lineCount = nx::core::FileUtilities::LinesInFile(inputFilePath); + if(lineCount < 0) + { + return {MakeErrorResult(to_underlying(IssueCodes::FILE_NOT_OPEN), fmt::format("Could not open file for reading: {}", inputFilePath)), {}}; + } std::fstream in(inputFilePath.c_str(), std::ios_base::in); if(!in.is_open()) { @@ -429,17 +434,18 @@ IFilter::PreflightResult ReadCSVFileFilter::preflightImpl(const DataStructure& d s_HeaderCache[s_InstanceId].FilePath = readCSVData.inputFilePath; - usize lineCount = 0; + usize currentLine = 0; while(!in.eof()) { std::string line; std::getline(in, line); - lineCount++; + currentLine++; - if(headerMode == ReadCSVData::HeaderMode::LINE && lineCount == readCSVData.headersLine) + if(headerMode == ReadCSVData::HeaderMode::LINE && currentLine == readCSVData.headersLine) { s_HeaderCache[s_InstanceId].Headers = line; s_HeaderCache[s_InstanceId].HeadersLine = readCSVData.headersLine; + break; } } @@ -704,7 +710,6 @@ namespace namespace SIMPL { constexpr StringLiteral k_SelectedPathKey = "Wizard_SelectedPath"; -constexpr StringLiteral k_TupleDimsKey = "Wizard_TupleDims"; } // namespace SIMPL } // namespace diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleCentroidFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleCentroidFilter.cpp index 9b770df050..a55a54cad4 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleCentroidFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleCentroidFilter.cpp @@ -95,7 +95,7 @@ IFilter::PreflightResult TriangleCentroidFilter::preflightImpl(const DataStructu { DataPath createArrayDataPath = pTriangleGeometryDataPath.createChildPath(faceAttributeMatrix->getName()).createChildPath(pCentroidsArrayName); // Create the face areas DataArray Action and store it into the resultOutputActions - auto createArrayAction = std::make_unique(nx::core::DataType::float32, std::vector{triangleGeom->getNumberOfFaces()}, std::vector{3}, createArrayDataPath); + auto createArrayAction = std::make_unique(nx::core::DataType::float64, std::vector{triangleGeom->getNumberOfFaces()}, std::vector{3}, createArrayDataPath); resultOutputActions.value().appendAction(std::move(createArrayAction)); } diff --git a/src/Plugins/SimplnxCore/test/ReadCSVFileTest.cpp b/src/Plugins/SimplnxCore/test/ReadCSVFileTest.cpp index 266df30756..b8b6660989 100644 --- a/src/Plugins/SimplnxCore/test/ReadCSVFileTest.cpp +++ b/src/Plugins/SimplnxCore/test/ReadCSVFileTest.cpp @@ -20,8 +20,8 @@ using namespace nx::core; namespace { const fs::path k_TestInput = fs::path(unit_test::k_BinaryDir.view()) / "ReadCSVFileTest" / "Input.txt"; -constexpr int32 k_InvalidArgumentErrorCode = -100; -constexpr int32 k_OverflowErrorCode = -101; +constexpr int32 k_InvalidArgumentErrorCode = -10351; +constexpr int32 k_OverflowErrorCode = -10353; constexpr int32 k_BlankLineErrorCode = -119; constexpr int32 k_EmptyFile = -100; constexpr int32 k_InconsistentCols = -104; @@ -79,7 +79,21 @@ void CreateTestDataFile(const fs::path& inputFilePath, nonstd::span } } -// ----------------------------------------------------------------------------- +/** + * + * @param inputFilePath + * @param startImportRow + * @param headerMode + * @param headersLine + * @param delimiters + * @param customHeaders + * @param dataTypes + * @param skippedArrayMask + * @param tupleDims + * @param values + * @param newGroupName + * @return + */ Arguments createArguments(const std::string& inputFilePath, usize startImportRow, ReadCSVData::HeaderMode headerMode, usize headersLine, const std::vector& delimiters, const std::vector& customHeaders, const std::vector& dataTypes, const std::vector& skippedArrayMask, const std::vector& tupleDims, nonstd::span values, const std::string& newGroupName) @@ -159,10 +173,11 @@ void TestCase_TestPrimitives_Error(nonstd::span values, int32 expec std::string arrayName = "Array"; DataPath arrayPath = DataPath({newGroupName, arrayName}); + usize tupleCount = std::count_if(values.begin(), values.end(), [](const std::string& s) { return !s.empty(); }); + ReadCSVFileFilter filter; DataStructure dataStructure; - Arguments args = - createArguments(k_TestInput.string(), 2, ReadCSVData::HeaderMode::LINE, 1, {','}, {arrayName}, {GetDataType()}, {false}, {static_cast(values.size())}, values, newGroupName); + Arguments args = createArguments(k_TestInput.string(), 2, ReadCSVData::HeaderMode::LINE, 1, {','}, {arrayName}, {GetDataType()}, {false}, {tupleCount}, values, newGroupName); // Create the test input data file fs::create_directories(k_TestInput.parent_path()); @@ -542,37 +557,5 @@ TEST_CASE("SimplnxCore::ReadCSVFileFilter (Case 6): Invalid filter execution - B v = {std::to_string(std::numeric_limits::min()), "", std::to_string(std::numeric_limits::max())}; TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - // End line blank tests - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); - - v = {std::to_string(std::numeric_limits::min()), std::to_string(std::numeric_limits::max()), ""}; - TestCase_TestPrimitives_Error(v, k_BlankLineErrorCode); + // Blank lines at the end of the file are not counted in the line count } diff --git a/src/Plugins/SimplnxCore/test/TriangleCentroidTest.cpp b/src/Plugins/SimplnxCore/test/TriangleCentroidTest.cpp index 54ac5300d2..8863a04c92 100644 --- a/src/Plugins/SimplnxCore/test/TriangleCentroidTest.cpp +++ b/src/Plugins/SimplnxCore/test/TriangleCentroidTest.cpp @@ -95,7 +95,7 @@ TEST_CASE("SimplnxCore::TriangleCentroidFilter", "[SimplnxCore][TriangleCentroid std::vector centroids = {{0.5F, 0.333333F, 0.0F}, {3.0F, 0.333333, 0.0F}, {6.66667, 0.0F, 0.0F}, {9.0F, 0.0F, 0.0F}}; - const auto& calculatedCentroids = dataStructure.getDataRefAs(triangleCentroidsDataPath); + const auto& calculatedCentroids = dataStructure.getDataRefAs(triangleCentroidsDataPath); for(size_t t = 0; t < 4; t++) { diff --git a/src/simplnx/Utilities/DataArrayUtilities.hpp b/src/simplnx/Utilities/DataArrayUtilities.hpp index 94ecc317e8..14db78d6ea 100644 --- a/src/simplnx/Utilities/DataArrayUtilities.hpp +++ b/src/simplnx/Utilities/DataArrayUtilities.hpp @@ -42,15 +42,15 @@ namespace value = FUNCTION(input); \ } catch(const std::invalid_argument& e) \ { \ - return nx::core::MakeErrorResult(-100, fmt::format("Error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10351, fmt::format("Error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } catch(const std::out_of_range& e) \ { \ - return nx::core::MakeErrorResult(-101, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10353, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } \ \ if(value > std::numeric_limits::max() || value < std::numeric_limits::min()) \ { \ - return nx::core::MakeErrorResult(-101, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10353, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } \ \ return {static_cast(value)}; @@ -73,7 +73,7 @@ namespace { \ if(!input.empty() && input.at(0) == '-') \ { \ - return nx::core::MakeErrorResult(-101, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10353, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } \ \ SIMPLNX_DEF_STRING_CONVERTOR_INT(CONTAINER_TYPE, TYPE, FUNCTION) \ @@ -92,10 +92,10 @@ namespace value = static_cast(FUNCTION(input)); \ } catch(const std::invalid_argument& e) \ { \ - return nx::core::MakeErrorResult(-100, fmt::format("Error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10351, fmt::format("Error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } catch(const std::out_of_range& e) \ { \ - return nx::core::MakeErrorResult(-101, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ + return nx::core::MakeErrorResult(-10353, fmt::format("Overflow error trying to convert '{}' to type '{}' using function '{}'", input, #TYPE, #FUNCTION)); \ } \ return {value}; \ } \ diff --git a/src/simplnx/Utilities/FileUtilities.cpp b/src/simplnx/Utilities/FileUtilities.cpp new file mode 100644 index 0000000000..e47328078e --- /dev/null +++ b/src/simplnx/Utilities/FileUtilities.cpp @@ -0,0 +1,138 @@ +#include "FileUtilities.hpp" + +#include + +#include +#include + +namespace fs = std::filesystem; + +namespace nx::core::FileUtilities +{ + +int64 LinesInFile(const std::string& filepath) +{ + const usize BUFFER_SIZE = 16384; + usize lines = 0; + usize bytes = 0; + + FILE* fd = fopen(filepath.c_str(), "rb"); + if(nullptr == fd) + { + return -1; + } + + // Check if the very last character is NOT a newline character + fseek(fd, -1, SEEK_END); + char last[1]; + fread(last, 1, 1, fd); + if(last[0] != '\n') + { + lines++; + } + rewind(fd); + + // Read through the rest of the file + char buf[BUFFER_SIZE + 1]; + while(true) + { + memset(buf, 0, BUFFER_SIZE + 1); + size_t bytes_read = fread(buf, 1, BUFFER_SIZE, fd); + if(bytes_read == 0) + { + break; + } + + bytes += bytes_read; + char* end = buf + bytes_read; + usize buflines = 0; + + for(char* p = buf; p < end; p++) + { + buflines += *p == '\n'; + } + lines += buflines; + } + fclose(fd); + return lines; +} + +Result<> ValidateCSVFile(const std::string& filePath) +{ + constexpr int64_t bufferSize = 2048; + + auto absPath = fs::absolute(filePath); + + if(!fs::exists({absPath})) + { + return MakeErrorResult(-300, fmt::format("File does not exist: {}", absPath.string())); + } + + // Obtain the file size + const size_t fileSize = fs::file_size(absPath); + + // Open the file + std::ifstream in(absPath.c_str(), std::ios_base::binary); + if(!in.is_open()) + { + return MakeErrorResult(-301, fmt::format("Could not open file for reading: {}", absPath.string())); + } + + size_t actualSize = bufferSize; + if(fileSize <= bufferSize) + { + actualSize = fileSize; + } + + // Allocate the buffer + std::vector buffer(actualSize, 0); + + // Copy the file contents into the buffer + try + { + in.read(buffer.data(), actualSize); + } catch(const std::exception& e) + { + return MakeErrorResult(-302, fmt::format("There was an error reading the data from file: {}. Exception: {}", absPath.string(), e.what())); + } + + // Check the buffer for invalid characters, tab characters, new-line characters, and carriage return characters + bool hasNewLines = false; + bool hasCarriageReturns = false; + bool hasTabs = false; + // If the first line of the file is > 2048 then this will fail! (MJ) + for(size_t i = 0; i < actualSize; i++) + { + const char currentChar = buffer[i]; + + if(currentChar < 32 && currentChar != 9 && currentChar != 10 && currentChar != 13) + { + // This is an unprintable character + return MakeErrorResult(-303, fmt::format("Unprintable characters have been detected in file: {}. Please import a different file.", absPath.string())); + } + if(currentChar == 9) + { + hasTabs = true; + } + else if(currentChar == 10) + { + hasNewLines = true; + } + else if(currentChar == 13) + { + hasCarriageReturns = true; + } + } + + if(!hasNewLines && !hasCarriageReturns && !hasTabs) + { + // This might be a binary file + return MakeErrorResult(-304, fmt::format("The file \"{}\" might be a binary file, because line-feed, tab, or carriage return characters have not been detected. Using this file may crash the " + "program or cause unexpected results. Please import a different file.", + absPath.string())); + } + + return {}; +} + +} // namespace nx::core::FileUtilities diff --git a/src/simplnx/Utilities/FileUtilities.hpp b/src/simplnx/Utilities/FileUtilities.hpp index f34d277fda..60a7c3133c 100644 --- a/src/simplnx/Utilities/FileUtilities.hpp +++ b/src/simplnx/Utilities/FileUtilities.hpp @@ -31,95 +31,27 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #pragma once -#include -#include -#include #include #include "simplnx/Common/Result.hpp" -namespace fs = std::filesystem; - namespace nx::core { namespace FileUtilities { -Result<> ValidateCSVFile(const std::string& filePath) -{ - constexpr int64_t bufferSize = 2048; - - auto absPath = fs::absolute(filePath); - - if(!fs::exists({absPath})) - { - return MakeErrorResult(-300, fmt::format("File does not exist: {}", absPath.string())); - } - - // Obtain the file size - const size_t fileSize = fs::file_size(absPath); - - // Open the file - std::ifstream in(absPath.c_str(), std::ios_base::binary); - if(!in.is_open()) - { - return MakeErrorResult(-301, fmt::format("Could not open file for reading: {}", absPath.string())); - } - - size_t actualSize = bufferSize; - if(fileSize <= bufferSize) - { - actualSize = fileSize; - } - - // Allocate the buffer - std::vector buffer(actualSize, 0); - - // Copy the file contents into the buffer - try - { - in.read(buffer.data(), actualSize); - } catch(const std::exception& e) - { - return MakeErrorResult(-302, fmt::format("There was an error reading the data from file: {}. Exception: {}", absPath.string(), e.what())); - } - - // Check the buffer for invalid characters, tab characters, new-line characters, and carriage return characters - bool hasNewLines = false; - bool hasCarriageReturns = false; - bool hasTabs = false; - // If the first line of the file is > 2048 then this will fail! (MJ) - for(size_t i = 0; i < actualSize; i++) - { - const char currentChar = buffer[i]; - - if(currentChar < 32 && currentChar != 9 && currentChar != 10 && currentChar != 13) - { - // This is an unprintable character - return MakeErrorResult(-303, fmt::format("Unprintable characters have been detected in file: {}. Please import a different file.", absPath.string())); - } - if(currentChar == 9) - { - hasTabs = true; - } - else if(currentChar == 10) - { - hasNewLines = true; - } - else if(currentChar == 13) - { - hasCarriageReturns = true; - } - } - - if(!hasNewLines && !hasCarriageReturns && !hasTabs) - { - // This might be a binary file - return MakeErrorResult(-304, fmt::format("The file \"{}\" might be a binary file, because line-feed, tab, or carriage return characters have not been detected. Using this file may crash the " - "program or cause unexpected results. Please import a different file.", - absPath.string())); - } - return {}; -} +/** + * @brief + * @param filepath + * @return + */ +SIMPLNX_EXPORT int64 LinesInFile(const std::string& filepath); + +/** + * @brief + * @param filePath + * @return + */ +SIMPLNX_EXPORT Result<> ValidateCSVFile(const std::string& filePath); } // namespace FileUtilities } // namespace nx::core