├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── LICENSE.rtf ├── README.md ├── cmake ├── FindNSIS.cmake ├── FindWIX.cmake └── FortranHelper.cmake ├── codecov.yml ├── contrib └── cmake │ ├── BuildFCSV.cmake │ ├── FindFCSV.cmake │ ├── IncludeFCSV.cmake │ └── README.md ├── csv-fortran.code-workspace ├── files ├── csv_test.out.ref ├── test.csv ├── test2.csv ├── test2.csv.ref ├── test_2_columns.csv └── test_write.csv.ref ├── ford.md ├── fpm.toml ├── media ├── logo.png └── logo.svg ├── src ├── csv_kinds.f90 ├── csv_module.F90 ├── csv_parameters.f90 └── csv_utilities.f90 └── test ├── csv_test.f90 ├── csv_test2.f90 ├── csv_test3.f90 └── csv_test4.f90 /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | 5 | Build: 6 | runs-on: ${{ matrix.os }} 7 | permissions: 8 | contents: write 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu-latest] 13 | gcc_v: [12, 13] # gfortran versions to test 14 | python-version: [3.12] 15 | env: 16 | FC: gfortran-${{ matrix.gcc_v }} 17 | GCC_V: ${{ matrix.gcc_v }} 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v3 22 | with: 23 | submodules: recursive 24 | 25 | - name: Install Python 26 | if: contains( matrix.gcc_v, 12 ) 27 | uses: actions/setup-python@v5.4.0 # Use pip to install latest CMake, & FORD/Jin2For, etc. 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Setup Graphviz 32 | if: contains( matrix.gcc_v, 12 ) 33 | uses: ts-graphviz/setup-graphviz@v1 34 | 35 | - name: Setup Fortran Package Manager 36 | uses: fortran-lang/setup-fpm@v7 37 | with: 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | - name: Install Python dependencies 41 | if: contains( matrix.gcc_v, 12 ) 42 | run: | 43 | python -m pip install --upgrade pip 44 | pip install ford numpy matplotlib 45 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 46 | 47 | - name: Install GFortran Linux 48 | run: | 49 | sudo apt-get install lcov 50 | sudo update-alternatives \ 51 | --install /usr/bin/gcc gcc /usr/bin/gcc-${{ matrix.gcc_v }} 100 \ 52 | --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-${{ matrix.gcc_v }} \ 53 | --slave /usr/bin/gcov gcov /usr/bin/gcov-${{ matrix.gcc_v }} 54 | 55 | # - name: Compile 56 | # run: fpm build --profile release 57 | 58 | - name: Run tests 59 | run: fpm test --profile debug --flag -coverage 60 | 61 | - name: Create coverage report 62 | if: contains( matrix.gcc_v, 12 ) 63 | run: | 64 | mkdir -p ${{ env.COV_DIR }} 65 | mv ./build/gfortran_*/*/* ${{ env.COV_DIR }} 66 | lcov --capture --initial --base-directory . --directory ${{ env.COV_DIR }} --output-file ${{ env.COV_DIR }}/coverage.base 67 | lcov --capture --base-directory . --directory ${{ env.COV_DIR }} --output-file ${{ env.COV_DIR }}/coverage.capture 68 | lcov --add-tracefile ${{ env.COV_DIR }}/coverage.base --add-tracefile ${{ env.COV_DIR }}/coverage.capture --output-file ${{ env.COV_DIR }}/coverage.info 69 | env: 70 | COV_DIR: build/coverage 71 | 72 | - name: Upload coverage report 73 | if: contains( matrix.gcc_v, 12 ) 74 | uses: codecov/codecov-action@v3 75 | with: 76 | files: build/coverage/coverage.info 77 | 78 | - name: Build documentation 79 | if: contains( matrix.gcc_v, 12 ) 80 | run: ford ./ford.md 81 | 82 | - name: Deploy Documentation 83 | if: contains( matrix.gcc_v, 12 ) && github.ref == 'refs/heads/master' 84 | uses: JamesIves/github-pages-deploy-action@v4.7.3 85 | with: 86 | branch: gh-pages # The branch the action should deploy to. 87 | folder: doc # The folder the action should deploy. 88 | single-commit: true 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Directories 31 | build 32 | doc 33 | lib 34 | bin 35 | 36 | # mac 37 | .DS_Store 38 | 39 | # tests 40 | /*.csv -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # cmake_minimum_required(VERSION 3.6) #maybe ok? 2 | cmake_minimum_required(VERSION 3.17) 3 | 4 | # Needed since 3.19.4; not sure why... 5 | cmake_policy(SET CMP0053 NEW) 6 | 7 | # Append local CMake module directory 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 9 | 10 | # Set package metadata 11 | set(CPACK_PACKAGE_NAME "csv-fortran") 12 | set(CPACK_PACKAGE_VERSION_MAJOR "2") 13 | set(CPACK_PACKAGE_VERSION_MINOR "0") 14 | set(CPACK_PACKAGE_VERSION_PATCH "2") 15 | set(CPACK_PACKAGE_VERSION_TWEAK "0") 16 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}.${CPACK_PACKAGE_VERSION_TWEAK}") 17 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A modern Fortran library for reading and writing CSV (comma-separated value) files.A modern Fortran library for reading and writing CSV (comma-separated value) files.") 18 | # set(CPACK_PACKAGE_VENDOR "") 19 | set(CPACK_PACKAGE_CONTACT "Jacob Williams") 20 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 21 | set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/jacobwilliams/csv-fortran") 22 | # See https://spdx.org/licenses/ 23 | set(SPDX_LICENSE_TAG "BSD-3-Clause") 24 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 25 | # Note: This should not change for the life of the product 26 | # Generated from guidgen.exe 27 | set(CPACK_WIX_UPGRADE_GUID "C4EE52E4-336B-4702-97EA-8F3A192365BF") 28 | 29 | # RAA: What is this? Base installation directory for NSIS installer? 30 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") 31 | string(TIMESTAMP BUILD_TIMESTAMP UTC) 32 | 33 | # Show your work... 34 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 35 | 36 | # Set project name and language 37 | project(csv-fortran 38 | LANGUAGES Fortran 39 | VERSION "${CPACK_PACKAGE_VERSION}") 40 | 41 | ############################################################################### 42 | ## Options #################################################################### 43 | ############################################################################### 44 | 45 | # Set fortran compiler with environment variable FC or CMake option 46 | # -D CMAKE_Fortran_COMPILER 47 | 48 | # WiX installer search directories (root and binary directories) 49 | # Note the quotes around the entire combination of option, type specifier, and value 50 | # -D "WIX_BINARY_DIR:PATH=/Program Files (x86)/WiX Toolset v3.11/bin" 51 | # Only one of these options is necessary; "/bin" is appended to WIX_ROOT_DIR for detecting WiX binaries 52 | # If possible, just set WIX_BINARY_DIR to the directory containing candle.exe and light.exe 53 | set(WIX_BINARY_DIR "/Program Files (x86)/WiX Toolset v3.11/bin" CACHE PATH "Path to WiX binaries") 54 | set(WIX_ROOT_DIR "/Program Files (x86)/WiX Toolset v3.11" CACHE PATH "Path to WiX installation") 55 | 56 | ############################################################################### 57 | ## Dependencies and CMake Modules ############################################ 58 | ############################################################################### 59 | 60 | # Defined cmake_parse_arguments(). Needed only for CMake 3.4 and earlier 61 | include(CMakeParseArguments) 62 | 63 | # Set default installation paths; should be invoked after setting project language(s) 64 | include(GNUInstallDirs) 65 | 66 | # CTest setup 67 | # Needed for valgrind (usually only enable_testing() is needed) 68 | include(CTest) 69 | enable_testing() 70 | 71 | # Manage Fortran compiler options, library linking and .mod file inclusion 72 | include(FortranHelper) 73 | 74 | ############################################################################### 75 | ## Build ###################################################################### 76 | ############################################################################### 77 | 78 | list(APPEND FCOPTS 79 | ${FCOPT_WALL} 80 | ${FCOPT_FCHECKALL} 81 | ${FCOPT_DEBUG} 82 | ${FCOPT_BACKTRACE} 83 | ) 84 | 85 | # Set recent language standard if available 86 | # if(${FC_ALLOWS_STD_F2018}) 87 | # list(APPEND FCOPTS ${FCOPT_STD_F2018}) 88 | # else 89 | if(${FC_ALLOWS_STD_F2008}) 90 | list(APPEND FCOPTS ${FCOPT_STD_F2008}) 91 | endif() 92 | 93 | if(${FC_ALLOWS_NO_OPTIMIZATION}) 94 | list(APPEND FCOPTS ${FCOPT_NO_OPTIMIZATION}) 95 | endif() 96 | 97 | message(STATUS "Fortran compiler options for csv-fortran set to ${FCOPTS}") 98 | 99 | # Target FCSV: csv-fortran library 100 | set(FCSV_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") 101 | 102 | # Define sources for FCSV library 103 | set(FCSV_SOURCES 104 | "${FCSV_SOURCE_DIR}/csv_kinds.f90" 105 | "${FCSV_SOURCE_DIR}/csv_parameters.f90" 106 | "${FCSV_SOURCE_DIR}/csv_utilities.f90" 107 | "${FCSV_SOURCE_DIR}/csv_module.F90" 108 | ) 109 | 110 | # Define FCSV library artifact 111 | set(LIBFCSV_NAME fcsv) 112 | set(LIBFCSV_FORTRAN_MODULE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${LIBFCSV_NAME}_include") 113 | file(MAKE_DIRECTORY "${LIBFCSV_FORTRAN_MODULE_DIR}") 114 | 115 | add_library(${LIBFCSV_NAME} STATIC ${FCSV_SOURCES}) 116 | 117 | set_target_properties( 118 | ${LIBFCSV_NAME} 119 | PROPERTIES 120 | Fortran_MODULE_DIRECTORY ${LIBFCSV_FORTRAN_MODULE_DIR} 121 | ) 122 | 123 | target_compile_options(${LIBFCSV_NAME} PUBLIC ${FCOPTS}) 124 | 125 | install(TARGETS ${LIBFCSV_NAME}) 126 | 127 | ### Test executables 128 | 129 | # Target : csv-fortran library 130 | set(TEST_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test") 131 | 132 | # add_executable(csv_write_test "${TEST_SOURCE_DIR}/csv_write_test.f90") 133 | # target_link_libraries(csv_write_test ${LIBFCSV_NAME}) 134 | # target_compile_options(csv_write_test PUBLIC ${FCOPTS}) 135 | # install(TARGETS csv_write_test) 136 | 137 | # add_executable(csv_read_test "${TEST_SOURCE_DIR}/csv_read_test.f90") 138 | # target_link_libraries(csv_read_test ${LIBFCSV_NAME}) 139 | # target_compile_options(csv_read_test PUBLIC ${FCOPTS}) 140 | # install(TARGETS csv_read_test) 141 | 142 | # add_executable(csv_local_test "${TEST_SOURCE_DIR}/csv_local_test.f90") 143 | # target_link_libraries(csv_local_test ${LIBFCSV_NAME}) 144 | # target_compile_options(csv_local_test PUBLIC ${FCOPTS}) 145 | # install(TARGETS csv_local_test) 146 | 147 | add_executable(csv_test "${TEST_SOURCE_DIR}/csv_test.f90") 148 | target_link_libraries(csv_test ${LIBFCSV_NAME}) 149 | target_compile_options(csv_test PUBLIC ${FCOPTS}) 150 | install(TARGETS csv_test) 151 | 152 | set_target_properties( 153 | # csv_write_test csv_read_test csv_local_test csv_test 154 | csv_test 155 | PROPERTIES 156 | Fortran_MODULE_DIRECTORY ${LIBFCSV_FORTRAN_MODULE_DIR} 157 | ) 158 | 159 | 160 | ############################################################################### 161 | ## Testing #################################################################### 162 | ############################################################################### 163 | set(TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/test") 164 | # ***** Pre-Test File and Directory Generation ***** 165 | 166 | # Create test output as separate target folded into `make all` 167 | add_custom_target(fcsv_test_setup 168 | ALL 169 | DEPENDS csv_test 170 | COMMAND ${CMAKE_COMMAND} -E echo "Copying test and reference data" 171 | COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "${TEST_DIR}" 172 | VERBATIM 173 | USES_TERMINAL 174 | ) 175 | 176 | # # Create csv_write_test output as separate target folded into `make all` 177 | # add_custom_target(run_csv_write_test 178 | # ALL 179 | # DEPENDS fcsv_test_setup 180 | # COMMAND ${CMAKE_COMMAND} -E echo "Running csv_write_test" 181 | # COMMAND $ 182 | # BYPRODUCTS test_write.csv 183 | # WORKING_DIRECTORY "${TEST_DIR}" 184 | # VERBATIM 185 | # USES_TERMINAL 186 | # ) 187 | 188 | # # Create csv_read_test output as separate target folded into `make all` 189 | # add_custom_target(run_csv_read_test 190 | # ALL 191 | # DEPENDS run_csv_write_test 192 | # COMMAND ${CMAKE_COMMAND} -E echo "Running csv_read_test" 193 | # COMMAND $ > csv_read_test.out 194 | # BYPRODUCTS csv_read_test.out 195 | # WORKING_DIRECTORY "${TEST_DIR}" 196 | # VERBATIM 197 | # USES_TERMINAL 198 | # ) 199 | 200 | # # Create csv_local_test output as separate target folded into `make all` 201 | # add_custom_target(run_csv_local_test 202 | # ALL 203 | # DEPENDS fcsv_test_setup 204 | # COMMAND ${CMAKE_COMMAND} -E echo "Running csv_local_test" 205 | # COMMAND $ > csv_test.out 206 | # BYPRODUCTS csv_test.out test2.csv 207 | # WORKING_DIRECTORY "${TEST_DIR}" 208 | # VERBATIM 209 | # USES_TERMINAL 210 | # ) 211 | 212 | # Create csv_local_test output as separate target folded into `make all` 213 | add_custom_target(run_csv_local_test 214 | ALL 215 | DEPENDS fcsv_test_setup 216 | COMMAND ${CMAKE_COMMAND} -E echo "Running csv_local_test" 217 | COMMAND $ > csv_test.out 218 | BYPRODUCTS csv_test.out test2.csv test_write.csv 219 | WORKING_DIRECTORY "${TEST_DIR}" 220 | VERBATIM 221 | USES_TERMINAL 222 | ) 223 | 224 | # ***** Integral Tests ***** 225 | 226 | # Tests using CMake's simple built-in "compare_files" command 227 | # Compare output with original reference data 228 | 229 | # # Example 1: csv_read_test.out 230 | # add_test(NAME 1_csv_read_test 231 | # COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol csv_read_test.out csv_read_test.out.ref 232 | # CONFIGURATIONS Debug Release "" 233 | # ) 234 | 235 | # Example 2: csv_test.out 236 | add_test(NAME 2_csv_test 237 | COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol csv_test.out csv_test.out.ref 238 | CONFIGURATIONS Debug Release "" 239 | ) 240 | 241 | # Example 3: test_write.csv 242 | add_test(NAME 3_test_write 243 | COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol test_write.csv test_write.csv.ref 244 | CONFIGURATIONS Debug Release "" 245 | ) 246 | 247 | # Example 4: test2.csv 248 | add_test(NAME 4_test2 249 | COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol test2.csv test2.csv.ref 250 | CONFIGURATIONS Debug Release "" 251 | ) 252 | 253 | set_tests_properties( 254 | # 1_csv_read_test 255 | 2_csv_test 256 | 3_test_write 257 | 4_test2 258 | PROPERTIES 259 | DEPENDS fcsv_test_setup 260 | # DEPENDS run_csv_write_test 261 | # DEPENDS run_csv_read_test 262 | DEPENDS run_csv_test 263 | WORKING_DIRECTORY "${TEST_DIR}" 264 | TIMEOUT 30 265 | ) 266 | 267 | ############################################################################### 268 | ## Analysis ################################################################### 269 | ############################################################################### 270 | 271 | # If valgrind or equivalent is available, memory leak/access tests can be run 272 | # with: 273 | # ctest -T memcheck 274 | # 275 | # Manual memory checks: 276 | # valgrind --leak-check=full --track-origins=yes --show-leak-kinds=all 277 | # "c:\Program Files (x86)\Dr. Memory\bin64\drmemory.exe" -ignore_kernel 278 | 279 | ############################################################################### 280 | ## Documentation ############################################################## 281 | ############################################################################### 282 | 283 | # TODO: Use add_custom_target() to run FORD against source tree then 284 | # use install() to package resulting documentation 285 | 286 | ############################################################################### 287 | ## Packaging ################################################################## 288 | ############################################################################### 289 | 290 | # Property data files 291 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/files/" 292 | DESTINATION files) 293 | 294 | # Documentation files 295 | install(FILES LICENSE README.md 296 | DESTINATION doc) 297 | 298 | # Test files (source files; test data is in ./files) 299 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/test" 300 | DESTINATION test 301 | FILES_MATCHING 302 | PATTERN *.f90 303 | ) 304 | 305 | # Fortran module files 306 | install(FILES "${LIBFCSV_FORTRAN_MODULE_DIR}/csv_module.mod" 307 | DESTINATION finclude) 308 | 309 | list(APPEND CPACK_GENERATOR ZIP) 310 | 311 | if(WIN32) 312 | # Set up NSIS 313 | find_package(NSIS) 314 | if(NSIS_FOUND) 315 | # set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/img/csv-fortran-icon.ico") 316 | # set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/img/csv-fortran-icon.ico") 317 | set(CPACK_NSIS_INSTALLED_ICON_NAME "Uninstall.exe") 318 | set(CPACK_NSIS_HELP_LINK "${CPACK_PACKAGE_HOMEPAGE_URL}") 319 | set(CPACK_NSIS_URL_INFO_ABOUT "${CPACK_PACKAGE_HOMEPAGE_URL}") 320 | set(CPACK_NSIS_MODIFY_PATH ON) 321 | set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) 322 | 323 | list(APPEND CPACK_GENERATOR NSIS) 324 | endif() 325 | 326 | # NuGet 327 | # TODO: Find a more robust means of detecting whether NuGet is available 328 | find_program(NUGET_EXECUTABLE nuget) 329 | if(NUGET_EXECUTABLE) 330 | 331 | install(FILES LICENSE 332 | RENAME LICENSE.txt 333 | DESTINATION doc) 334 | 335 | # install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/img/csv-fortran-icon.png" 336 | # DESTINATION img) 337 | 338 | # set(CPACK_NUGET_COMPONENT_INSTALL OFF) 339 | set(CPACK_NUGET_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") 340 | set(CPACK_NUGET_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") 341 | set(CPACK_NUGET_PACKAGE_DESCRIPTION "A CSV library for Modern Fortran") 342 | set(CPACK_NUGET_PACKAGE_AUTHORS jacobwilliams) 343 | set(CPACK_NUGET_PACKAGE_TITLE "${CPACK_PACKAGE_NAME}") 344 | set(CPACK_NUGET_PACKAGE_OWNERS jacobwilliams) 345 | set(CPACK_NUGET_PACKAGE_HOMEPAGE_URL "${CPACK_PACKAGE_HOMEPAGE_URL}") 346 | # LicenseURL is deprecated; how to set License with CMake/CPack? 347 | # set(CPACK_NUGET_PACKAGE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 348 | # set(CPACK_NUGET_PACKAGE_LICENSEURL "https://github.com/jacobwilliams/csv-fortran/blob/master/LICENSE") 349 | set(CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION "${SPDX_LICENSE_TAG}") 350 | # set(CPACK_NUGET_PACKAGE_LICENSE_FILE_NAME "doc/LICENSE.txt") 351 | # A URL for a 64x64 image with transparency background to use as the icon for the package in UI display. 352 | # set(CPACK_NUGET_PACKAGE_ICONURL "https://example.com/img/csv-fortran-icon.png") 353 | # set(CPACK_NUGET_PACKAGE_ICON "img/csv-fortran-icon.png") 354 | set(CPACK_NUGET_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") 355 | set(CPACK_NUGET_PACKAGE_RELEASE_NOTES "Experimental version for testing NuGet packaging. For information only.") 356 | set(CPACK_NUGET_PACKAGE_COPYRIGHT "Copyright 2021, Jacob Williams") 357 | set(CPACK_NUGET_PACKAGE_LANGUAGE "en_US") 358 | # A space-delimited list of tags and keywords that describe the package and aid discoverability of packages through search and filtering. 359 | set(CPACK_NUGET_PACKAGE_TAGS "CSV" "comma separated values" "Fortran") 360 | # # Repository info 361 | # set(CPACK_NUGET_PACKAGE_REPOSITORY_TYPE "git") 362 | # set(CPACK_NUGET_PACKAGE_REPOSITORY_URL "https://github.com/jacobwilliams/csv-fortran.git") 363 | # set(CPACK_NUGET_PACKAGE_REPOSITORY_BRANCH "1.2.1") 364 | # set(CPACK_NUGET_PACKAGE_REPOSITORY_COMMIT "77a50e78c527bba3bd655b4400b") 365 | # A list of package dependencies. 366 | # set(CPACK_NUGET_PACKAGE_DEPENDENCIES TBD) 367 | # A version specification for the particular dependency, where is an item of the dependency list (see above) transformed with MAKE_C_IDENTIFIER function of string() command. 368 | # CPACK_NUGET_PACKAGE_DEPENDENCIES__VERSION 369 | # 370 | set(CPACK_NUGET_PACKAGE_DEBUG OFF) 371 | 372 | list(APPEND CPACK_GENERATOR NuGet) 373 | endif() 374 | 375 | # Set up WIX 376 | # These config variables are set at the top of CMakeLists.txt 377 | # set(WIX_BINARY_DIR "/Program Files (x86)/WiX Toolset v3.11/bin" CACHE PATH "Path to WiX binaries") 378 | # set(WIX_ROOT_DIR "/Program Files (x86)/WiX Toolset v3.11" CACHE PATH "Path to WiX installation") 379 | find_package(WIX) 380 | if(WIX_FOUND) 381 | message(STATUS "WIX was found: WIX_FOUND = ${WIX_FOUND}") 382 | # Manually tell CPack where find_package() found WIX... 383 | set(CPACK_WIX_ROOT "${WIX_ROOT}") 384 | # Manually convert LICENSE to RTF format because WIX/CPack is stupid 385 | set(WIX_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rtf") 386 | set(CPACK_WIX_LICENSE_RTF "${WIX_LICENSE_FILE}") 387 | install(FILES "${WIX_LICENSE_FILE}" DESTINATION doc) 388 | # set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/img/csv-fortran-icon.ico") 389 | 390 | list(APPEND CPACK_GENERATOR WIX) 391 | else() 392 | message(STATUS "WIX was not found: WIX_FOUND = ${WIX_FOUND}") 393 | endif() 394 | else() 395 | list(APPEND CPACK_GENERATOR TGZ TBZ2) 396 | if(APPLE) 397 | # Set up DRAGNDROP 398 | # Add DragNDrop properties 399 | set(CPACK_DMG_VOLUME_NAME "${CPACK_PACKAGE_NAME} v${CPACK_PACKAGE_VERSION}") 400 | # set(CPACK_DMG_FORMAT "UDZO") 401 | set(CPACK_DMG_FORMAT "UDBZ") 402 | #* CPACK_DMG_DS_STORE 403 | #* CPACK_DMG_DS_STORE_SETUP_SCRIPT 404 | #* CPACK_DMG_BACKGROUND_IMAGE 405 | # CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK 406 | # CPACK_DMG_SLA_DIR 407 | # CPACK_DMG_SLA_LANGUAGES 408 | # CPACK_DMG__FILE_NAME 409 | # CPACK_COMMAND_HDIUTIL 410 | # CPACK_COMMAND_SETFILE 411 | # CPACK_COMMAND_REZ 412 | list(APPEND CPACK_GENERATOR DragNDrop) 413 | else() 414 | # Set up DEB 415 | # TODO: Find a more robust means of detecting whether debian packaging should be enabled 416 | # Note that readelf is not strictly necessary but platform is assumed 417 | # Debian-ish if it's present 418 | find_program(READELF_EXECUTABLE readelf) 419 | if(READELF_EXECUTABLE) 420 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION 421 | "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") 422 | # set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any") 423 | # Auto-detect dependencies 424 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 425 | # Hack to find internal libraries - see https://gitlab.kitware.com/cmake/cmake/-/issues/17447 426 | # list(APPEND CMAKE_INSTALL_RPATH 427 | # "$ORIGIN/../lib/graphviz" 428 | # ) 429 | # A better solution is to set LD_LIBRARY_PATH when running CPack or `make package` 430 | # Exanple: LD_LIBRARY_PATH=./_CPack_Packages/Linux/DEB/Graphviz-2.45.0-Linux/usr/lib/graphviz:${LD_LIBRARY_PATH} make package 431 | # Exanple: LD_LIBRARY_PATH=./_CPack_Packages/Linux/DEB/Graphviz-2.45.0-Linux/usr/lib/graphviz:${LD_LIBRARY_PATH} cpack -G DEB 432 | # Build multiple packages 433 | set(CPACK_DEB_COMPONENT_INSTALL ON) 434 | set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON) 435 | # set(CPACK_DEBIAN_PACKAGE_DEBUG ON) 436 | # Turn off for executable-only; only needed for packaging libraries 437 | set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS OFF) 438 | 439 | list(APPEND CPACK_GENERATOR DEB) 440 | endif() 441 | 442 | # Set up RPM 443 | # TODO: Find a more robust means of detecting whether RPM generator is available 444 | find_program(RPMBUILD_EXECUTABLE rpmbuild) 445 | if(RPMBUILD_EXECUTABLE) 446 | # message(STATUS "rpmbuild found at ${RPMBUILD_EXECUTABLE}; needs configuration") 447 | 448 | # Needs additional work (maybe?) 449 | set(CPACK_RPM_PACKAGE_LICENSE "${SPDX_LICENSE_TAG}") 450 | set(CPACK_RPM_FILE_NAME RPM-DEFAULT) 451 | list(APPEND CPACK_GENERATOR RPM) 452 | endif() 453 | endif() 454 | endif() 455 | 456 | # This must be last 457 | include(CPack) 458 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Fortran CSV Module 2 | https://github.com/jacobwilliams/csv-fortran 3 | 4 | Copyright (c) 2017-2022, Jacob Williams 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this 14 | list of conditions and the following disclaimer in the documentation and/or 15 | other materials provided with the distribution. 16 | 17 | * The names of its contributors may not be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LICENSE.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Courier New;}} 2 | {\*\generator Riched20 10.0.19041}\viewkind4\uc1 3 | \pard\f0\fs18\lang1033 Fortran CSV Module\par 4 | https://github.com/jacobwilliams/fortran-csv-module\par 5 | \par 6 | Copyright (c) 2017-2022, Jacob Williams\par 7 | All rights reserved.\par 8 | \par 9 | Redistribution and use in source and binary forms, with or without modification,\par 10 | are permitted provided that the following conditions are met:\par 11 | \par 12 | * Redistributions of source code must retain the above copyright notice, this\par 13 | list of conditions and the following disclaimer.\par 14 | \par 15 | * Redistributions in binary form must reproduce the above copyright notice, this\par 16 | list of conditions and the following disclaimer in the documentation and/or\par 17 | other materials provided with the distribution.\par 18 | \par 19 | * The names of its contributors may not be used to endorse or promote products\par 20 | derived from this software without specific prior written permission.\par 21 | \par 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\par 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\par 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\par 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\par 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\par 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\par 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\par 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\par 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\par 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\par 32 | \par 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![csv-fortran](media/logo.png) 2 | ============ 3 | 4 | [![Language](https://img.shields.io/badge/-Fortran-734f96?logo=fortran&logoColor=white)](https://github.com/topics/fortran) 5 | [![GitHub release](https://img.shields.io/github/release/jacobwilliams/csv-fortran.svg)](https://github.com/jacobwilliams/csv-fortran/releases/latest) 6 | [![Build Status](https://github.com/jacobwilliams/csv-fortran/actions/workflows/CI.yml/badge.svg)](https://github.com/jacobwilliams/csv-fortran/actions) 7 | [![codecov](https://codecov.io/gh/jacobwilliams/csv-fortran/branch/master/graph/badge.svg?token=43HK33CSMY)](https://codecov.io/gh/jacobwilliams/csv-fortran) 8 | [![last-commit](https://img.shields.io/github/last-commit/jacobwilliams/csv-fortran)](https://github.com/jacobwilliams/csv-fortran/commits/master) 9 | 10 | ### Description 11 | 12 | A modern Fortran library for reading and writing CSV (comma-separated value) files. 13 | 14 | ### Latest Release 15 | 16 | [![GitHub release](https://img.shields.io/github/release/jacobwilliams/csv-fortran.svg?style=plastic)](https://github.com/jacobwilliams/csv-fortran/releases/latest) 17 | 18 | ### Documentation 19 | 20 | The latest API documentation for the `master` branch can be found [here](https://jacobwilliams.github.io/csv-fortran/). This was generated from the source code using [FORD](https://github.com/Fortran-FOSS-Programmers/ford). 21 | 22 | 23 | ### Getting started 24 | #### Get the code 25 | ```bash 26 | git clone https://github.com/jacobwilliams/csv-fortran 27 | cd csv-fortran 28 | ``` 29 | #### Dependencies 30 | 1. Git 31 | 2. [fpm](https://github.com/fortran-lang/fpm) or [CMake](https://cmake.org) 32 | 3. [FORD](https://github.com/Fortran-FOSS-Programmers/ford) (optional) 33 | 34 | #### Build with [fortran-lang/fpm](https://github.com/fortran-lang/fpm) 35 | Fortran Package Manager (fpm) is a great package manager and build system for Fortran. 36 | You can build using provided `fpm.toml`: 37 | ```bash 38 | fpm build 39 | ``` 40 | To use `csv-fortran` within your fpm project, add the following to your `fpm.toml` file: 41 | ```toml 42 | [dependencies] 43 | csv-fortran = { git="https://github.com/jacobwilliams/csv-fortran.git" } 44 | ``` 45 | 46 | ### Examples 47 | 48 | Everything is handled by an object-oriented `csv_file` class. Here is an example for writing a file: 49 | 50 | ```fortran 51 | program csv_write_test 52 | 53 | use csv_module 54 | use iso_fortran_env, only: wp => real64 55 | 56 | implicit none 57 | 58 | type(csv_file) :: f 59 | logical :: status_ok 60 | 61 | ! set optional inputs: 62 | call f%initialize(verbose = .true.) 63 | 64 | ! open the file 65 | call f%open('test.csv',n_cols=4,status_ok=status_ok) 66 | 67 | ! add header 68 | call f%add(['x','y','z','t']) 69 | call f%next_row() 70 | 71 | ! add some data: 72 | call f%add([1.0_wp,2.0_wp,3.0_wp],real_fmt='(F5.3)') 73 | call f%add(.true.) 74 | call f%next_row() 75 | call f%add([4.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 76 | call f%add(.false.) 77 | call f%next_row() 78 | 79 | ! finished 80 | call f%close(status_ok) 81 | 82 | end program csv_write_test 83 | ``` 84 | 85 | Which produces the following file: 86 | ``` 87 | x,y,z,t 88 | 1.000,2.000,3.000,T 89 | 4.000,5.000,6.000,F 90 | ``` 91 | 92 | Real, integer, logical, or character data can be added as scalars, vectors, and matrices. 93 | 94 | When reading a CSV file, the data is stored internally in the class as allocatable character strings, which can be retrieved as real, integer, logical or character vectors as necessary. For example, to get the `x`, `y`, `z`, and `t` vectors from the previously-generated file: 95 | 96 | ```fortran 97 | program csv_read_test 98 | 99 | use csv_module 100 | use iso_fortran_env, only: wp => real64 101 | 102 | implicit none 103 | 104 | type(csv_file) :: f 105 | character(len=30),dimension(:),allocatable :: header 106 | real(wp),dimension(:),allocatable :: x,y,z 107 | logical,dimension(:),allocatable :: t 108 | logical :: status_ok 109 | integer,dimension(:),allocatable :: itypes 110 | 111 | ! read the file 112 | call f%read('test.csv',header_row=1,status_ok=status_ok) 113 | 114 | ! get the header and type info 115 | call f%get_header(header,status_ok) 116 | call f%variable_types(itypes,status_ok) 117 | 118 | ! get some data 119 | call f%get(1,x,status_ok) 120 | call f%get(2,y,status_ok) 121 | call f%get(3,z,status_ok) 122 | call f%get(4,t,status_ok) 123 | 124 | ! destroy the file 125 | call f%destroy() 126 | 127 | end program csv_read_test 128 | ``` 129 | 130 | Various options are user-selectable for specifying the format (e.g., changing the quote or delimiter characters). You can choose to enclose strings (or all fields) in quotes or not. The library works pretty well, and there are probably additional improvements that could be made. For one thing, it doesn't properly handle the case of a string that contains the delimiter character (I'll eventually fix this). If anybody has any other improvements, fork it and send me a pull request. 131 | 132 | ### License 133 | 134 | This library is released under a [BSD-3 license](https://github.com/jacobwilliams/csv-fortran/blob/master/LICENSE). 135 | -------------------------------------------------------------------------------- /cmake/FindNSIS.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE for details. 3 | 4 | #[=======================================================================[.rst: 5 | FindNSIS 6 | --------- 7 | 8 | Find ``makensis`` executable. 9 | 10 | The module defines the following variables: 11 | 12 | ``NSIS_MAKE`` 13 | path to the ``makensis`` program 14 | 15 | ``NSIS_VERSION`` 16 | version of ``makensis`` 17 | 18 | ``NSIS_FOUND`` 19 | "True" if the program ``makensis`` was found 20 | 21 | The minimum required version of ``NSIS`` can be specified using the 22 | standard CMake syntax, e.g. :command:`find_package(NSIS 2.1.3)`. 23 | 24 | Example usage: 25 | 26 | .. code-block:: cmake 27 | 28 | find_package(NSIS) 29 | #]=======================================================================] 30 | 31 | # Input: 32 | # Set -D "NSIS_BINARY_DIR:PATH=/Program Files (x86)/NSIS" to specify 33 | # directory containing makensis.exe 34 | set(NSIS_BINARY_DIR "/Program Files (x86)/NSIS" 35 | CACHE PATH "Directory containing makensis.exe for NSIS packaging") 36 | 37 | # CMake does not allow for braces in $ENV{}, so a temporary variable must be used. 38 | set(PROGRAMFILES_X86 "ProgramFiles(x86)") 39 | 40 | find_program(NSIS_MAKE 41 | NAMES makensis 42 | PATHS ${NSIS_BINARY_DIR} $ENV{PROGRAMFILES}/NSIS $ENV{${PROGRAMFILES_X86}}/NSIS 43 | DOC "Path to the makensis executable" 44 | ) 45 | 46 | if(EXISTS "${NSIS_MAKE}") 47 | execute_process(COMMAND "${NSIS_MAKE}" /VERSION 48 | OUTPUT_VARIABLE NSIS_MAKE_OUTPUT_VARIABLE 49 | OUTPUT_STRIP_TRAILING_WHITESPACE 50 | ERROR_QUIET) 51 | # Version string looks like "" 52 | string(REGEX REPLACE "^.*v([0-9\\.]+)" "\\1" NSIS_VERSION "${NSIS_MAKE_OUTPUT_VARIABLE}") 53 | unset(NSIS_MAKE_OUTPUT_VARIABLE) 54 | endif() 55 | 56 | include(FindPackageHandleStandardArgs) 57 | #simple find_package_handle_standard_args(NSIS DEFAULT_MSG NSIS_MAKE) 58 | find_package_handle_standard_args(NSIS 59 | REQUIRED_VARS NSIS_MAKE 60 | VERSION_VAR NSIS_VERSION) 61 | 62 | mark_as_advanced( 63 | NSIS_MAKE 64 | ) -------------------------------------------------------------------------------- /cmake/FindWIX.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE for details. 3 | 4 | #[=======================================================================[.rst: 5 | FindWIX 6 | --------- 7 | 8 | Find components of ``WIX`` package. 9 | 10 | The module defines the following variables: 11 | 12 | ``WIX_FOUND`` 13 | "True" if the programs ``candle`` and ``light`` wer found 14 | 15 | ``WIX_ROOT`` 16 | path to the directory containing the ``candle`` program 17 | 18 | ``WIX_VERSION_STRING`` 19 | version of ``candle`` 20 | 21 | ``WIX_CANDLE`` 22 | path to the ``candle`` program 23 | 24 | ``WIX_LIGHT`` 25 | path to the ``light`` program 26 | 27 | ``WIX_DARK`` 28 | path to the ``dark`` program 29 | 30 | ``WIX_HEAT`` 31 | path to the ``heat`` program 32 | 33 | ``WIX_INSIGNIA`` 34 | path to the ``insignia`` program 35 | 36 | ``WIX_LIT`` 37 | path to the ``lit`` program 38 | 39 | ``WIX_LUX`` 40 | path to the ``lux`` program 41 | 42 | ``WIX_MELT`` 43 | path to the ``melt`` program 44 | 45 | ``WIX_NIT`` 46 | path to the ``nit`` program 47 | 48 | ``WIX_PYRO`` 49 | path to the ``pyro`` program 50 | 51 | ``WIX_SHINE`` 52 | path to the ``shine`` program 53 | 54 | ``WIX_SMOKE`` 55 | path to the ``smoke`` program 56 | 57 | ``WIX_THMVIEWER`` 58 | path to the ``ThmViewer`` program 59 | 60 | ``WIX_TORCH`` 61 | path to the ``torch`` program 62 | 63 | ``WIX_WIXCOP`` 64 | path to the ``WixCop`` program 65 | 66 | The minimum required version of ``WIX`` can be specified using the 67 | standard CMake syntax, e.g. :command:`find_package(WIX 2.1.3)`. 68 | 69 | Example usage: 70 | 71 | .. code-block:: cmake 72 | 73 | find_package(WIX) 74 | #]=======================================================================] 75 | 76 | # Original coding 77 | # 2009/02 Petr Pytelka (pyta at lightcomp.cz) 78 | # Retrieved from https://gitlab.kitware.com/cmake/community/-/wikis/contrib/modules/FindWix 79 | # 80 | # WIX homepage has moved to https://wixtoolset.org/ 81 | # -- Bob Apthorpe (bob.apthorpe at gmail.com) 20200620 82 | # Cleaned syntax and logic 83 | # -- Bob Apthorpe (bob.apthorpe at gmail.com) 20201012 84 | # Substantially revised and modernized; removeed legacy macros 85 | # -- Bob Apthorpe (bob.apthorpe at gmail.com) 20201020 86 | # 87 | # - Try to find Windows Installer XML 88 | # See http://wix.sourceforge.net 89 | # 90 | # The follwoing variables are optionally searched for defaults 91 | # WIX_ROOT_DIR: Base directory of WIX2 tree to use. 92 | # 93 | # The following are set after configuration is done: 94 | # WIX_FOUND 95 | # WIX_ROOT 96 | # WIX_VERSION_STRING 97 | # WIX_CANDLE 98 | # WIX_LIGHT 99 | # WIX_DARK 100 | # WIX_HEAT 101 | # WIX_INSIGNIA 102 | # WIX_LIT 103 | # WIX_LUX 104 | # WIX_MELT 105 | # WIX_NIT 106 | # WIX_PYRO 107 | # WIX_SHINE 108 | # WIX_SMOKE 109 | # WIX_THMVIEWER 110 | # WIX_TORCH 111 | # WIX_WIXCOP 112 | 113 | # Typical root dirs of installations, exactly one of them is used 114 | set(WIX_POSSIBLE_BIN_DIRS 115 | "${WIX_BINARY_DIR}" 116 | "${WIX_ROOT_DIR}/bin" 117 | "$ENV{WIX}/bin" 118 | "$ENV{WIX_ROOT_DIR}/bin" 119 | "$ENV{ProgramFiles}/Windows Installer XML/bin" 120 | "$ENV{ProgramFiles}/WiX Toolset v3.11/bin" 121 | ) 122 | 123 | # WiX functionality requires at least candle.exe and light.exe 124 | 125 | find_program(WIX_CANDLE 126 | NAMES candle 127 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 128 | ) 129 | 130 | if(EXISTS "${WIX_CANDLE}") 131 | execute_process(COMMAND "${WIX_CANDLE}" -help 132 | OUTPUT_VARIABLE WIX_CANDLE_OUTPUT_VARIABLE 133 | OUTPUT_STRIP_TRAILING_WHITESPACE 134 | ERROR_QUIET) 135 | # First line of help output looks like 136 | # "Windows Installer XML Toolset Compiler version 3.11.2.4516" 137 | # Remaining lines can be dropped; regex fragment ' *\n.*' trims output 138 | string(REGEX REPLACE "^Windows.*version ([0-9\\.]+) *\n.*" "\\1" 139 | WIX_VERSION_STRING "${WIX_CANDLE_OUTPUT_VARIABLE}") 140 | unset(WIX_CANDLE_OUTPUT_VARIABLE) 141 | endif() 142 | 143 | # Lesser tools 144 | find_program(WIX_LIGHT 145 | NAMES light 146 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 147 | ) 148 | 149 | find_program(WIX_DARK 150 | NAMES dark 151 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 152 | ) 153 | 154 | find_program(WIX_HEAT 155 | NAMES heat 156 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 157 | ) 158 | 159 | find_program(WIX_INSIGNIA 160 | NAMES insignia 161 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 162 | ) 163 | 164 | find_program(WIX_LIT 165 | NAMES lit 166 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 167 | ) 168 | 169 | find_program(WIX_LUX 170 | NAMES lux 171 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 172 | ) 173 | 174 | find_program(WIX_MELT 175 | NAMES melt 176 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 177 | ) 178 | 179 | find_program(WIX_NIT 180 | NAMES nit 181 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 182 | ) 183 | 184 | find_program(WIX_PYRO 185 | NAMES pyro 186 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 187 | ) 188 | 189 | find_program(WIX_SHINE 190 | NAMES shine 191 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 192 | ) 193 | 194 | find_program(WIX_SMOKE 195 | NAMES smoke 196 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 197 | ) 198 | 199 | find_program(WIX_THMVIEWER 200 | NAMES ThmViewer 201 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 202 | ) 203 | 204 | find_program(WIX_TORCH 205 | NAMES torch 206 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 207 | ) 208 | 209 | find_program(WIX_WIXCOP 210 | NAMES WixCop 211 | PATHS ${WIX_POSSIBLE_BIN_DIRS} 212 | ) 213 | 214 | include(FindPackageHandleStandardArgs) 215 | # find_package_handle_standard_args(WIX DEFAULT_MSG 216 | # WIX_CANDLE WIX_LIGHT) 217 | find_package_handle_standard_args(WIX 218 | REQUIRED_VARS WIX_CANDLE WIX_LIGHT 219 | VERSION_VAR WIX_VERSION_STRING) 220 | 221 | # Set WiX root directory based on location of candle.exe 222 | if(WIX_FOUND) 223 | # message(STATUS "WiX version: ${WIX_VERSION_STRING}") 224 | get_filename_component(WIX_BINARY_DIR_ "${WIX_CANDLE}" DIRECTORY) 225 | get_filename_component(WIX_ROOT "${WIX_BINARY_DIR_}/.." ABSOLUTE) 226 | endif() 227 | 228 | mark_as_advanced( 229 | WIX_ROOT 230 | WIX_CANDLE 231 | WIX_LIGHT 232 | WIX_DARK 233 | WIX_HEAT 234 | WIX_INSIGNIA 235 | WIX_LIT 236 | WIX_LUX 237 | WIX_MELT 238 | WIX_NIT 239 | WIX_PYRO 240 | WIX_SHINE 241 | WIX_SMOKE 242 | WIX_THMVIEWER 243 | WIX_TORCH 244 | WIX_WIXCOP 245 | ) 246 | -------------------------------------------------------------------------------- /cmake/FortranHelper.cmake: -------------------------------------------------------------------------------- 1 | # Detect available compiler options 2 | include(CheckFortranCompilerFlag) 3 | 4 | # Set variable name fcopt_name to $fc_flag and fcopt_allowed to 1 (True) 5 | # if $fc_flag is a legal, quiet option to the Fortran compiler 6 | function(set_fcopt fcopt_allowed fcopt_name fc_flag) 7 | check_fortran_compiler_flag("${fc_flag}" ${fcopt_allowed}) 8 | if(${${fcopt_allowed}}) 9 | set(${fcopt_name} "${fc_flag}" PARENT_SCOPE) 10 | else() 11 | set(${fcopt_name} "" PARENT_SCOPE) 12 | endif() 13 | endfunction() 14 | 15 | # Set option flag visibility and values 16 | set_fcopt(FC_ALLOWS_NO_OPTIMIZATION FCOPT_NO_OPTIMIZATION "-O0") 17 | set_fcopt(FC_ALLOWS_DEBUG_OPTIMIZATION FCOPT_DEBUG_OPTIMIZATION "-Og") 18 | # set_fcopt(FC_ALLOWS_STD_LEGACY FCOPT_STD_LEGACY "--std=legacy") 19 | set_fcopt(FC_ALLOWS_WALL FCOPT_WALL "-Wall") 20 | set_fcopt(FC_ALLOWS_BACKTRACE FCOPT_BACKTRACE "-fbacktrace") 21 | set_fcopt(FC_ALLOWS_DEBUG FCOPT_DEBUG "-g") 22 | set_fcopt(FC_ALLOWS_SAVE FCOPT_SAVE "-fno-automatic") 23 | set_fcopt(FC_ALLOWS_FCHECKALL FCOPT_FCHECKALL "-fcheck=all") 24 | 25 | set_fcopt(FC_ALLOWS_STD_F2008 FCOPT_STD_F2008 "--std=f2008") 26 | # set_fcopt(FC_ALLOWS_STD_F2018 FCOPT_STD_F2018 "--std=f2018") 27 | 28 | # Code coverage options - experimental 29 | set_fcopt(FC_ALLOWS_COVERAGE FCOPT_COVERAGE "--coverage") 30 | set_fcopt(FC_ALLOWS_PROFILE_ARCS FCOPT_PROFILE_ARCS "-fprofile-arcs") 31 | set_fcopt(FC_ALLOWS_TEST_COVERAGE FCOPT_TEST_COVERAGE "-ftest-coverage") 32 | 33 | # Add Fortran_MODULE_DIRECTORY for each Fortran library included in a 34 | # target 35 | function(link_fortran_libraries my_target) 36 | target_link_libraries(${my_target} ${ARGN}) 37 | foreach(f_lib IN LISTS ARGN) 38 | target_include_directories(${my_target} PUBLIC $) 39 | endforeach() 40 | endfunction() -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: header, changes, diff, sunburst 3 | coverage: 4 | ignore: 5 | - test 6 | - doc 7 | status: 8 | patch: 9 | default: 10 | target: 70% 11 | project: 12 | default: 13 | target: 60% 14 | -------------------------------------------------------------------------------- /contrib/cmake/BuildFCSV.cmake: -------------------------------------------------------------------------------- 1 | if(GIT_FOUND) 2 | # Retrieve, build, and install csv-fortran (aka FCSV) 3 | # distribution from GitHub 4 | set(FCSV_DIST_DIR "${CMAKE_CURRENT_BINARY_DIR}/FCSV-source") 5 | 6 | set(FCSV_LOCAL_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/FCSV-artifacts") 7 | 8 | # Note: "" is interpolated within ExternalProject_Add to 9 | # FCSV_LOCAL_INSTALL_DIR 10 | # list(APPEND FCSV_CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX:PATH=") 11 | list(APPEND FCSV_CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX:PATH=${FCSV_LOCAL_INSTALL_DIR}") 12 | 13 | ExternalProject_Add( 14 | FCSV_external 15 | # Note: Use URL and URL_HASH [SHA512|SHA256|MD5]=4A54C0DE... to 16 | # download and checksum an archive. Note that URL may refer to a 17 | # local file, allowing this to work without net access. 18 | # GIT_REPOSITORY https://github.com/jacobwilliams/csv-fortran.git 19 | # GIT_TAG 1.2.0 20 | GIT_REPOSITORY https://github.com/jacobwilliams/csv-fortran 21 | GIT_TAG 1.3.1 22 | SOURCE_DIR "${FCSV_DIST_DIR}" 23 | INSTALL_DIR "${FCSV_LOCAL_INSTALL_DIR}" 24 | CMAKE_ARGS ${FCSV_CMAKE_ARGS} 25 | BUILD_BYPRODUCTS ${FCSV_LOCAL_INSTALL_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}fcsv${CMAKE_STATIC_LIBRARY_SUFFIX} 26 | LOG_BUILD YES 27 | USES_TERMINAL_DOWNLOAD YES 28 | USES_TERMINAL_UPDATE YES 29 | ) 30 | 31 | # From csv-fortran/CMakeLists.txt: 32 | # ... 33 | # # Set default installation paths; should be invoked after setting project language(s) 34 | # include(GNUInstallDirs) 35 | # ... 36 | # # Fortran module files 37 | # install(FILES "${LIBFCSV_FORTRAN_MODULE_DIR}/csv_module.mod" 38 | # DESTINATION finclude) 39 | # ... 40 | 41 | # Create ${FCSV_LOCAL_INSTALL_DIR}/finclude based on the module install location 42 | # set in csv-fortran/CMakeLists.txt. Creating this directory avoids a race 43 | # condition - see https://www.scivision.dev/cmake-fetchcontent-vs-external-project/ 44 | file(MAKE_DIRECTORY ${FCSV_LOCAL_INSTALL_DIR}/finclude) 45 | 46 | # Make the fcsv library available to the current project as an import 47 | add_library(fcsv STATIC IMPORTED GLOBAL) 48 | 49 | # Set properties on fcsv target to point at the installed library location and 50 | # the module directory created above. FCSV uses `include(GNUInstallDirs)` which 51 | # typically installs libraries to ./lib which is why the IMPORTED_LOCATION below 52 | # uses the path ${FCSV_LOCAL_INSTALL_DIR}/lib 53 | set_target_properties(fcsv 54 | PROPERTIES 55 | IMPORTED_LOCATION ${FCSV_LOCAL_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}fcsv${CMAKE_STATIC_LIBRARY_SUFFIX} 56 | INTERFACE_INCLUDE_DIRECTORIES ${FCSV_LOCAL_INSTALL_DIR}/finclude 57 | ) 58 | 59 | # To use this recipe, add one of the following fragments 60 | # to CMakeLists.txt after project(): 61 | # find_package(Git) 62 | # include(BuildFCSV) 63 | # or 64 | # find_package(Git) 65 | # include(/path/to/BuildFCSV.cmake) 66 | 67 | # To include the csv_module.mod link the fcsv library to the target 68 | # MyExecutable, add the following directives after 69 | # add_executable(MyExecutable ...): 70 | # ----- 71 | # target_link_libraries(MyExecutable fcsv) 72 | # target_include_directories(MyExecutable PUBLIC $) 73 | # add_dependencies(MyExecutable FCSV_external) 74 | # ----- 75 | 76 | else() 77 | message(STATUS "git not available; using fallback CSV source files") 78 | 79 | set(FCSV_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/contrib/csv-fortran/src") 80 | 81 | # Full path to csv_kinds.f90 82 | set(FCSV_KINDS_SRC "${FCSV_SOURCE_DIR}/csv_kinds.f90") 83 | 84 | # Full path to csv_parameters.f90 85 | set(FCSV_PARAMETERS_SRC "${FCSV_SOURCE_DIR}/csv_parameters.f90") 86 | 87 | # Full path to csv_utilities.f90 88 | set(FCSV_UTILITIES_SRC "${FCSV_SOURCE_DIR}/csv_utilities.f90") 89 | 90 | # Full path to csv_module.F90 91 | set(FCSV_MODULE_SRC "${FCSV_SOURCE_DIR}/csv_module.F90") 92 | 93 | list(APPEND FCSV_SRC_FILES "${FCSV_KINDS_SRC}" "${FCSV_PARAMETERS_SRC}" "${FCSV_UTILITIES_SRC}" "${FCSV_MODULE_SRC}") 94 | message(STATUS "Developer Note: Append contents of FCSV_SRC_FILES to list of sources to compile") 95 | 96 | message(STATUS "CSV source files are in ${FCSV_SOURCE_DIR}") 97 | 98 | endif() 99 | 100 | set(CSV_FOUND ON) 101 | # __END__ -------------------------------------------------------------------------------- /contrib/cmake/FindFCSV.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file LICENSE for details. 3 | 4 | #[=======================================================================[.rst: 5 | FindFCSV 6 | --------- 7 | 8 | Find ``FCSV`` unit testing library for Fortran. 9 | 10 | The module defines the following variables: 11 | 12 | ``FCSV_LIB_NAME`` 13 | ``FCSV`` library base name ('fcsv') 14 | 15 | ``FCSV_LIBRARY`` 16 | path to the ``FCSV`` library 17 | 18 | ``FCSV_LIBRARY_DIR`` 19 | path to the ``FCSV`` library directory 20 | 21 | ``FCSV_MODULE_FILE`` 22 | path to the ``FCSV`` Fortran module (.mod) file 23 | 24 | ``FCSV_MODULE_DIR`` 25 | path to the ``FCSV`` Fortran module directory 26 | 27 | ``FCSV_FOUND`` 28 | "True" if the ``FCSV`` library and module files were found 29 | 30 | Example usage: 31 | 32 | .. code-block:: cmake 33 | 34 | find_package(FCSV) 35 | #]=======================================================================] 36 | 37 | set(FCSV_LIB_NAME fcsv) 38 | set(FCSV_FOUND OFF) 39 | 40 | # Set FCSV_ROOT and FCSV_MODULE_PATH on the command line: 41 | # The following are defined in the root CMakeLists.txt file 42 | # set(FCSV_ROOT "" CACHE PATH "Installation root of FCSV library") 43 | # set(FCSV_MODULE_PATH "" CACHE PATH "Directory containing FCSV Fortran module (.mod) files") 44 | 45 | # BuildFCSV.cmake sets FCSV_ROOT to 46 | # \build\FCSV_external-prefix\src\FCSV_external-build 47 | 48 | if(IS_DIRECTORY "${FCSV_ROOT}") 49 | set(SEARCH_FCSV_LIB ${FCSV_ROOT}/lib) 50 | set(SEARCH_FCSV_MOD ${FCSV_ROOT}/include ${FCSV_ROOT}/module 51 | ${FCSV_ROOT}/finclude ${FCSV_ROOT}/finclude/fcsv) 52 | endif() 53 | 54 | if(IS_DIRECTORY "${FCSV_MODULE_PATH}") 55 | list(APPEND SEARCH_FCSV_MOD "${FCSV_MODULE_PATH}") 56 | endif() 57 | 58 | find_library(FCSV_LIBRARY 59 | NAMES "${FCSV_LIB_NAME}" 60 | PATHS ${SEARCH_FCSV_LIB} 61 | ) 62 | 63 | # message(STATUS "Debug: SEARCH_FCSV_MOD=${SEARCH_FCSV_MOD}") 64 | find_file(FCSV_MODULE_FILE 65 | NAMES "csv_module.mod" "${FCSV_LIB_NAME}.mod" 66 | PATHS ${SEARCH_FCSV_MOD} 67 | ) 68 | 69 | # Set FCSV_FOUND if both FCSV_LIBRARY and FCSV_MODULE_FILE are found 70 | include(FindPackageHandleStandardArgs) 71 | find_package_handle_standard_args(FCSV DEFAULT_MSG 72 | FCSV_LIBRARY FCSV_MODULE_FILE) 73 | 74 | if(FCSV_FOUND) 75 | ##### Set Output Variables ##### 76 | 77 | # Set the following: 78 | # - FCSV_LIB_NAME (at top; "fcsv") 79 | # - FCSV_LIBRARY_DIR 80 | # - FCSV_MODULE_DIR 81 | # - FCSV_MODULE_FILE (from find_file()) 82 | get_filename_component(FCSV_LIBRARY_DIR "${FCSV_LIBRARY}" DIRECTORY) 83 | get_filename_component(FCSV_MODULE_DIR "${FCSV_MODULE_FILE}" DIRECTORY) 84 | message(STATUS "Found FCSV library under ${FCSV_LIBRARY_DIR}") 85 | else() 86 | message(STATUS "Cannot find FCSV (is FCSV_ROOT set? '${FCSV_ROOT}')") 87 | endif() 88 | 89 | # These variables are set to be compatible with the naming scheme used 90 | # in original FCSV example CMake setup; see 91 | # build/FCSV-source/examples/example1/CMakeLists.txt 92 | # - FCSV_LIB_NAME (= "fcsv") 93 | # - FCSV_LIBRARY_DIR 94 | # - FCSV_MODULE_DIR 95 | 96 | # Note: This needs to be manually added to the list of source files 97 | # required for unit tests 98 | # - FCSV_MODULE_FILE 99 | -------------------------------------------------------------------------------- /contrib/cmake/IncludeFCSV.cmake: -------------------------------------------------------------------------------- 1 | # Retrieve csv-fortran from external source and include into project 2 | # See https://github.com/jacobwilliams/csv-fortran 3 | # 4 | #!!! Verify all these! 5 | # The following output variables are set by the FCSV subproject 6 | # - FCSV_LIB_NAME --> LIBFCSV_NAME 7 | # - FCSV_LIBRARY_DIR --> ??? 8 | # - FCSV_MODULE_DIR --> LIBFCSV_FORTRAN_MODULE_DIR 9 | set(FCSV_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/FCSV-source") 10 | 11 | FetchContent_Declare( 12 | FCSV_external 13 | GIT_REPOSITORY https://github.com/jacobwilliams/csv-fortran.git 14 | GIT_TAG 1.3.1 15 | SOURCE_DIR "${FCSV_SOURCE_DIR}" 16 | ) 17 | 18 | FetchContent_MakeAvailable(FCSV_external) 19 | FetchContent_GetProperties(FCSV_external) 20 | # FetchContent_GetProperties(FCSV_external 21 | # POPULATED FCSV_external_POPULATED 22 | # ) 23 | 24 | # To use this recipe, add one of the following include() lines 25 | # to CMakeLists.txt after project(): 26 | # include(IncludeFCSV) 27 | # or 28 | # include(/path/to/IncludeFCSV.cmake) 29 | 30 | # To include the csv_module.mod link the fcsv library to the target 31 | # MyExecutable, add the following directives after 32 | # add_executable(MyExecutable ...): 33 | # ----- 34 | # target_link_libraries(MyExecutable fcsv) 35 | # target_include_directories(MyExecutable PUBLIC $) 36 | # add_dependencies(MyExecutable fcsv) 37 | # ----- 38 | 39 | # set(FCSV_LIB_NAME "${LIBFCSV_NAME}") 40 | # # set(FCSV_LIBRARY_DIR --generator expression giving path of TARGET:${LIBFCSV_NAME}-- 41 | # set(FCSV_MODULE_DIR "${LIBFCSV_FORTRAN_MODULE_DIR}") 42 | 43 | set(FCSV_FOUND "${FCSV_external_POPULATED}") 44 | # __END__ -------------------------------------------------------------------------------- /contrib/cmake/README.md: -------------------------------------------------------------------------------- 1 | # Contributed CMake Support 2 | 3 | The following CMake recipes are intended for developers using 4 | csv-fortran as a dependency in their own projects. 5 | 6 | Please read through the recipes before use; they contain 7 | documentation that will help integrate csv-fortran 8 | with a CMake based project. 9 | 10 | Each recipe has advantages and disadvantages as listed below. 11 | Only one should be needed but they may be chained such that 12 | CMake preferentially uses a local installation and falls back 13 | to pulling source code over the network or vice versa. 14 | 15 | ## FindFCSV.cmake 16 | 17 | FindFCSV searches the host system for a previously installed fcsv library 18 | (*e.g.* `libfcsv.a`) and the Fortran module file `csv_module.mod`. 19 | No version checking is performed and there will likely be problems 20 | if csv-fortran and the parent project are built with 21 | different Fortran compilers. 22 | 23 | ## BuildFCSV.cmake 24 | 25 | __Important__: Review and modify `GIT_REPOSITORY` and `GIT_TAG` before 26 | use; they currently point to a test fork of the library. Also, ensure that 27 | `include(Git)` and `include(ExternalProject)` are called in the parent 28 | project before this recipe. 29 | 30 | This recipe uses `ExternalProject_Add` to retrieve the csv-fortran 31 | project and build it as an standalone dependency of the parent 32 | project. Tests and compiler flags of FCSV are kept separate from the 33 | parent project. 34 | 35 | ## IncludeFCSV.cmake 36 | 37 | __Important__: Review and modify `GIT_REPOSITORY` and `GIT_TAG` before 38 | use; they currently point to a test fork of the library. Also, ensure that 39 | `include(Git)` and `include(FetchContent)` are called in the parent 40 | project before this recipe. 41 | 42 | This recipe uses `FetchContent` to retrieve the csv-fortran 43 | project and build it as part of the parent project. This has two 44 | important effects: compiler flags of the parent and child project 45 | are merged and CTest will run FCSV's tests in addition to those of 46 | the parent project. 47 | -------------------------------------------------------------------------------- /csv-fortran.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.trimTrailingWhitespace": true, 9 | "editor.insertSpaces": true, 10 | "editor.tabSize": 4, 11 | "editor.trimAutoWhitespace": true 12 | } 13 | } -------------------------------------------------------------------------------- /files/csv_test.out.ref: -------------------------------------------------------------------------------- 1 | 2 | ============================ 3 | csv_test_1 4 | ============================ 5 | 6 | read file: ./test.csv 7 | 8 | File: test.csv 9 | 10 | Header Type 11 | id 3 12 | first_name 1 13 | age 2 14 | ok 4 15 | 16 | print all the rows: 17 | 1 Julie 80.244 false 18 | 2 Jose 11.569 true 19 | 3 Lois 12.339 true 20 | 4 Walter 63.173 false 21 | 5 Timothy 57.063 true 22 | 6 Barbara 79.705 true 23 | 7 Douglas 19.347 false 24 | 8 Tina 16.909 true 25 | 9 Gregory 67.749 true 26 | 10 Larry 43.705 true 27 | 11 Tina 9.931 true 28 | 12 Kelly 72.657 true 29 | 13 Marilyn 10.901 true 30 | 14 Gregory 9.966 true 31 | 15 Earl 10.875 true 32 | 16 Evelyn 33.268 true 33 | 17 Carol 54.498 false 34 | 18 Alice 2.505 true 35 | 19 Randy 39.775 false 36 | 37 | get some vectors: 38 | 39 | age: 40 | 80.244 41 | 11.569 42 | 12.339 43 | 63.173 44 | 57.063 45 | 79.705 46 | 19.347 47 | 16.909 48 | 67.749 49 | 43.705 50 | 9.931 51 | 72.657 52 | 10.901 53 | 9.966 54 | 10.875 55 | 33.268 56 | 54.498 57 | 2.505 58 | 39.775 59 | read file: ./test_2_columns.csv 60 | 61 | File: test_2_columns.csv 62 | 63 | Header Type 64 | id 3 65 | first_name 1 66 | 67 | print all the rows: 68 | 2 Jose 69 | 3 Lois 70 | 4 Walter 71 | 72 | get some vectors: 73 | 74 | name: 75 | Jose 76 | Lois 77 | Walter 78 | 79 | ============================ 80 | csv_write_test 81 | ============================ 82 | 83 | 84 | ============================ 85 | csv_read_test 86 | ============================ 87 | 88 | x= 1.0000000000000000 4.0000000000000000 89 | y= 2.0000000000000000 5.0000000000000000 90 | z= 3.0000000000000000 6.0000000000000000 91 | t= T F 92 | -------------------------------------------------------------------------------- /files/test.csv: -------------------------------------------------------------------------------- 1 | id,first_name,age,ok 2 | 1,Julie,80.244,false 3 | 2,Jose,11.569,true 4 | 3,Lois,12.339,true 5 | 4,Walter,63.173,false 6 | 5,Timothy,57.063,true 7 | 6,Barbara,79.705,true 8 | 7,Douglas,19.347,false 9 | 8,Tina,16.909,true 10 | 9,Gregory,67.749,true 11 | 10,Larry,43.705,true 12 | 11,Tina,9.931,true 13 | 12,Kelly,72.657,true 14 | 13,Marilyn,10.901,true 15 | 14,Gregory,9.966,true 16 | 15,Earl,10.875,true 17 | 16,Evelyn,33.268,true 18 | 17,Carol,54.498,false 19 | 18,Alice,2.505,true 20 | 19,Randy,39.775,false 21 | -------------------------------------------------------------------------------- /files/test2.csv: -------------------------------------------------------------------------------- 1 | "header1", "header2" 2 | "text1", -2.73 3 | "text2", 0.24 4 | -------------------------------------------------------------------------------- /files/test2.csv.ref: -------------------------------------------------------------------------------- 1 | "x","y","z","t" 2 | 0.10000000000000000E+0001,0.20000000000000000E+0001,0.30000000000000000E+0001,T 3 | 4.000,5.000,6.000,F 4 | -------------------------------------------------------------------------------- /files/test_2_columns.csv: -------------------------------------------------------------------------------- 1 | id,first_name 2 | 1,Julie 3 | 2,Jose 4 | 3,Lois 5 | 4,Walter 6 | -------------------------------------------------------------------------------- /files/test_write.csv.ref: -------------------------------------------------------------------------------- 1 | "x","y","z","t" 2 | 1.000,2.000,3.000,T 3 | 4.000,5.000,6.000,F 4 | -------------------------------------------------------------------------------- /ford.md: -------------------------------------------------------------------------------- 1 | project: csv-fortran 2 | project_dir: ./src 3 | output_dir: ./doc 4 | media_dir: ./media 5 | project_github: https://github.com/jacobwilliams/csv-fortran 6 | summary: Read and Write CSV Files Using Modern Fortran 7 | author: Jacob Williams 8 | github: https://github.com/jacobwilliams 9 | predocmark_alt: > 10 | predocmark: < 11 | docmark_alt: 12 | docmark: ! 13 | display: public 14 | protected 15 | private 16 | source: true 17 | graph: true 18 | extra_mods: iso_fortran_env:https://gcc.gnu.org/onlinedocs/gfortran/ISO_005fFORTRAN_005fENV.html 19 | 20 | {!README.md!} 21 | -------------------------------------------------------------------------------- /fpm.toml: -------------------------------------------------------------------------------- 1 | name = "csv-fortran" 2 | version = "2.1.0" 3 | author = "Jacob Williams" 4 | maintainer = "Jacob Williams" 5 | copyright = "Copyright (c) 2017-2023, Jacob Williams" 6 | license = "BSD-3" 7 | description = "Read and Write CSV Files Using Modern Fortran" 8 | homepage = "https://github.com/jacobwilliams/csv-fortran" 9 | categories = ["io"] 10 | keywords = ["csv"] 11 | 12 | [library] 13 | source-dir = "src" 14 | 15 | [install] 16 | library = true 17 | 18 | [build] 19 | auto-executables = true 20 | auto-examples = true 21 | auto-tests = true -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobwilliams/csv-fortran/7c6c04a3df1c44083ba1a2a0a96d2c0ff18ebef2/media/logo.png -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 25 | 32 | 33 | 36 | 43 | 44 | 46 | 50 | 54 | 55 | 63 | 68 | 74 | 79 | 84 | 90 | 91 | 92 | 118 | 120 | 121 | 123 | image/svg+xml 124 | 126 | 127 | 128 | 129 | 134 | 143 | CSV 154 | 157 | 165 | Fortran 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/csv_kinds.f90: -------------------------------------------------------------------------------- 1 | !******************************************************************************* 2 | !> 3 | ! Numeric kinds. 4 | 5 | module csv_kinds 6 | 7 | use iso_fortran_env, only: real128,real64,real32,int32 8 | 9 | private 10 | 11 | integer,parameter,public :: qp = real128 !! quad real kind 12 | integer,parameter,public :: wp = real64 !! default real kind 13 | integer,parameter,public :: sp = real32 !! additional real kind, single precision 14 | integer,parameter,public :: ip = int32 !! default integer kind 15 | 16 | end module csv_kinds 17 | !******************************************************************************* 18 | -------------------------------------------------------------------------------- /src/csv_module.F90: -------------------------------------------------------------------------------- 1 | !***************************************************************************************** 2 | !> author: Jacob Williams 3 | ! license: BSD 4 | ! 5 | ! For reading and writing CSV files. 6 | 7 | module csv_module 8 | 9 | use csv_utilities 10 | use csv_kinds 11 | use csv_parameters 12 | use iso_fortran_env, only: error_unit 13 | 14 | implicit none 15 | 16 | private 17 | 18 | ! the different types of variables that can be in a CSV file. 19 | integer,parameter,public :: csv_type_string = 1 !! a character string cell 20 | integer,parameter,public :: csv_type_double = 2 !! a `real(wp)` cell 21 | integer,parameter,public :: csv_type_integer = 3 !! an `integer(ip)` cell 22 | integer,parameter,public :: csv_type_logical = 4 !! a logical cell 23 | 24 | real(wp),parameter :: zero = 0.0_wp 25 | 26 | type,public :: csv_string 27 | !! a cell from a CSV file. 28 | !! 29 | !! This is used to store the data internally 30 | !! in the [[csv_file]] class. 31 | character(len=:),allocatable :: str 32 | end type csv_string 33 | 34 | type,public :: csv_file 35 | 36 | !! the main class for reading and writing CSV files. 37 | !! 38 | !!@note A CSV file is assumed to contain the same number 39 | !! of columns in each row. It may optionally contain 40 | !! a header row. 41 | 42 | private 43 | 44 | logical :: verbose = .false. !! to print error messages 45 | 46 | character(len=1) :: quote = '"' !! quotation character 47 | character(len=1) :: delimiter = ',' !! delimiter character 48 | 49 | ! for reading a csv file: 50 | integer :: n_rows = 0 !! number of rows in the file 51 | integer :: n_cols = 0 !! number of columns in the file 52 | integer :: chunk_size = 1024 !! for expanding vectors 53 | type(csv_string),dimension(:),allocatable :: header !! the header 54 | type(csv_string),dimension(:,:),allocatable :: csv_data !! the data in the file 55 | 56 | ! for writing a csv file: 57 | integer :: icol = 0 !! last column written in current row 58 | integer :: iunit = 0 !! file unit for writing 59 | logical :: enclose_strings_in_quotes = .true. !! if true, all string cells 60 | !! will be enclosed in quotes. 61 | logical :: enclose_all_in_quotes = .false. !! if true, *all* cells will 62 | !! be enclosed in quotes. 63 | character(len=1) :: logical_true_string = 'T' !! when writing a logical `true` 64 | !! value to a CSV file, this 65 | !! is the string to use 66 | !! (default is `T`) 67 | character(len=1) :: logical_false_string = 'F' !! when writing a logical `false` 68 | !! value to a CSV file, this 69 | !! is the string to use 70 | !! (default is `F`) 71 | 72 | contains 73 | 74 | private 75 | 76 | procedure,public :: initialize => initialize_csv_file 77 | procedure,public :: read => read_csv_file 78 | procedure,public :: destroy => destroy_csv_file 79 | 80 | procedure,public :: variable_types 81 | 82 | generic,public :: get_header => get_header_str,& 83 | get_header_csv_str 84 | procedure :: get_header_str 85 | procedure :: get_header_csv_str 86 | 87 | !> 88 | ! For getting data from the class 89 | ! after the file has been read. 90 | generic,public :: get => get_csv_data_as_str,& 91 | csv_get_value,& 92 | get_real_sp_column,& 93 | get_real_wp_column,& 94 | get_real_qp_column,& 95 | get_integer_column,& 96 | get_logical_column,& 97 | get_character_column,& 98 | get_csv_string_column 99 | procedure :: get_csv_data_as_str 100 | procedure :: csv_get_value 101 | procedure :: get_real_sp_column 102 | procedure :: get_real_wp_column 103 | procedure :: get_real_qp_column 104 | procedure :: get_integer_column 105 | procedure :: get_logical_column 106 | procedure :: get_character_column 107 | procedure :: get_csv_string_column 108 | 109 | procedure,public :: open => open_csv_file 110 | 111 | generic,public :: add => add_cell,& 112 | add_vector,& 113 | add_matrix 114 | procedure :: add_cell 115 | procedure :: add_vector 116 | procedure :: add_matrix 117 | 118 | procedure,public :: next_row 119 | procedure,public :: close => close_csv_file 120 | 121 | procedure :: tokenize => tokenize_csv_line 122 | procedure :: read_line_from_file 123 | procedure :: get_column 124 | 125 | end type csv_file 126 | 127 | contains 128 | !***************************************************************************************** 129 | 130 | !***************************************************************************************** 131 | !> 132 | ! Initialize a [[csv_file(type)]]. 133 | 134 | subroutine initialize_csv_file(me,quote,delimiter,& 135 | enclose_strings_in_quotes,& 136 | enclose_all_in_quotes,& 137 | logical_true_string,& 138 | logical_false_string,& 139 | chunk_size,& 140 | verbose) 141 | 142 | implicit none 143 | 144 | class(csv_file),intent(out) :: me 145 | character(len=1),intent(in),optional :: quote !! note: can only be one character 146 | !! (Default is `"`) 147 | character(len=1),intent(in),optional :: delimiter !! note: can only be one character 148 | !! (Default is `,`) 149 | logical,intent(in),optional :: enclose_strings_in_quotes !! if true, all string cells 150 | !! will be enclosed in quotes. 151 | !! (Default is True) 152 | logical,intent(in),optional :: enclose_all_in_quotes !! if true, *all* cells will 153 | !! be enclosed in quotes. 154 | !! (Default is False) 155 | character(len=1),intent(in),optional :: logical_true_string !! when writing a logical `true` 156 | !! value to a CSV file, this 157 | !! is the string to use 158 | !! (default is `T`) 159 | character(len=1),intent(in),optional :: logical_false_string !! when writing a logical `false` 160 | !! value to a CSV file, this 161 | !! is the string to use 162 | !! (default is `F`) 163 | integer,intent(in),optional :: chunk_size !! factor for expanding vectors 164 | !! (default is 100) 165 | logical,intent(in),optional :: verbose !! print error messages to the 166 | !! console (default is False) 167 | 168 | if (present(quote)) me%quote = quote 169 | if (present(delimiter)) me%delimiter = delimiter 170 | if (present(enclose_strings_in_quotes)) & 171 | me%enclose_strings_in_quotes = enclose_strings_in_quotes 172 | if (present(enclose_all_in_quotes)) & 173 | me%enclose_all_in_quotes = enclose_all_in_quotes 174 | if (present(logical_true_string)) & 175 | me%logical_true_string = logical_true_string 176 | if (present(logical_false_string)) & 177 | me%logical_false_string = logical_false_string 178 | if (present(verbose)) me%verbose = verbose 179 | if (present(chunk_size)) me%chunk_size = chunk_size 180 | 181 | ! override: 182 | if (me%enclose_all_in_quotes) me%enclose_strings_in_quotes = .true. 183 | 184 | end subroutine initialize_csv_file 185 | !***************************************************************************************** 186 | 187 | !***************************************************************************************** 188 | !> 189 | ! Destroy the data in a CSV file. 190 | 191 | subroutine destroy_csv_file(me) 192 | 193 | implicit none 194 | 195 | class(csv_file),intent(out) :: me 196 | 197 | end subroutine destroy_csv_file 198 | !***************************************************************************************** 199 | 200 | !***************************************************************************************** 201 | !> 202 | ! Read a CSV file. 203 | 204 | subroutine read_csv_file(me,filename,header_row,skip_rows,status_ok,delimiter) 205 | 206 | implicit none 207 | 208 | class(csv_file),intent(inout) :: me 209 | character(len=*),intent(in) :: filename !! the CSV file to open 210 | logical,intent(out) :: status_ok !! status flag 211 | integer,intent(in),optional :: header_row !! the header row 212 | integer,dimension(:),intent(in),optional :: skip_rows !! rows to skip 213 | character(len=1),intent(in),optional :: delimiter !! note: can only be one character 214 | !! (Default is `,`) 215 | 216 | type(csv_string),dimension(:),allocatable :: row_data !! a tokenized row 217 | integer,dimension(:),allocatable :: rows_to_skip !! the actual rows to skip 218 | character(len=:),allocatable :: line !! a line from the file 219 | integer :: i !! counter 220 | integer :: j !! counter 221 | integer :: irow !! row counter 222 | integer :: n_rows_in_file !! number of lines in the file 223 | integer :: n_rows !! number of rows in the output data matrix 224 | integer :: n_cols !! number of columns in the file (and output data matrix) 225 | integer :: istat !! open status flag 226 | integer :: iunit !! open file unit 227 | logical :: arrays_allocated !! if the arrays in the 228 | !! class have been allocated 229 | integer :: iheader !! row number of header row 230 | !! (0 if no header specified) 231 | character(len=1) :: tmp !! for skipping a row 232 | 233 | ! clear existing data: 234 | arrays_allocated = .false. 235 | if (allocated(me%csv_data)) deallocate(me%csv_data) 236 | if (allocated(me%header)) deallocate(me%header) 237 | if (present(delimiter)) me%delimiter = delimiter 238 | 239 | open(newunit=iunit, file=filename, status='OLD', iostat=istat) 240 | 241 | if (istat==0) then 242 | 243 | !get number of lines in the file 244 | n_rows_in_file = number_of_lines_in_file(iunit) 245 | 246 | !get number of lines in the data array 247 | if (present(skip_rows)) then 248 | !get size of unique elements in skip_rows, 249 | !and subtract from n_rows_in_file 250 | rows_to_skip = unique(skip_rows,chunk_size=me%chunk_size) 251 | n_rows = n_rows_in_file - size(rows_to_skip) 252 | else 253 | n_rows = n_rows_in_file 254 | end if 255 | if (present(header_row)) then 256 | iheader = max(0,header_row) 257 | n_rows = n_rows - merge(0,1,iheader==0) 258 | else 259 | iheader = 0 260 | end if 261 | 262 | me%n_rows = n_rows 263 | 264 | ! we don't know the number of columns 265 | ! until we parse the first row (or the header) 266 | 267 | !read each line in the file, parse it, and populate data 268 | irow = 0 269 | do i=1,n_rows_in_file !! rows in the file 270 | 271 | ! skip row if necessary 272 | if (allocated(rows_to_skip)) then 273 | if (any(i==rows_to_skip)) then 274 | read(iunit,fmt='(A1)',iostat=istat) tmp 275 | if (istat/=0) then 276 | if (me%verbose) write(error_unit,'(A)') & 277 | 'Error skipping row in file: '//trim(filename) 278 | close(unit=iunit,iostat=istat) 279 | status_ok = .false. 280 | return 281 | end if 282 | cycle 283 | end if 284 | end if 285 | 286 | call me%read_line_from_file(iunit,line,status_ok) 287 | if (.not. status_ok) return ! file read error 288 | call me%tokenize(line,row_data) 289 | 290 | if (.not. arrays_allocated) then 291 | ! note: the number of columns is obtained 292 | ! from the first one read. It is assumed 293 | ! that each row has the same number of 294 | ! columns. 295 | n_cols = size(row_data) 296 | me%n_cols = n_cols 297 | allocate(me%csv_data(n_rows,n_cols)) 298 | if (iheader/=0) allocate(me%header(n_cols)) 299 | arrays_allocated = .true. 300 | end if 301 | 302 | if (i==iheader) then 303 | do j=1,me%n_cols 304 | me%header(j)%str = row_data(j)%str 305 | end do 306 | else 307 | irow = irow + 1 !! row counter in data array 308 | do j=1,n_cols 309 | me%csv_data(irow,j) = row_data(j) !%str 310 | end do 311 | end if 312 | 313 | end do 314 | 315 | ! close the file 316 | close(unit=iunit,iostat=istat) 317 | 318 | status_ok = .true. 319 | 320 | else 321 | if (me%verbose) write(error_unit,'(A)') & 322 | 'Error opening file: '//trim(filename) 323 | status_ok = .false. 324 | end if 325 | 326 | end subroutine read_csv_file 327 | !***************************************************************************************** 328 | 329 | !***************************************************************************************** 330 | !> 331 | ! Open a CSV file for writing. 332 | ! 333 | ! Use `initialize` to set options for the CSV file. 334 | 335 | subroutine open_csv_file(me,filename,n_cols,status_ok,append) 336 | 337 | implicit none 338 | 339 | class(csv_file),intent(inout) :: me 340 | character(len=*),intent(in) :: filename !! the CSV file to open 341 | integer,intent(in) :: n_cols !! number of columns in the file 342 | logical,intent(out) :: status_ok !! status flag 343 | logical,intent(in),optional :: append !! append if file exists 344 | 345 | integer :: istat !! open `iostat` flag 346 | logical :: append_flag !! local copy of `append` argument 347 | logical :: file_exists !! if the file exists 348 | 349 | ! clear existing data: 350 | if (allocated(me%csv_data)) deallocate(me%csv_data) 351 | if (allocated(me%header)) deallocate(me%header) 352 | 353 | me%n_cols = n_cols 354 | 355 | ! optional append argument: 356 | append_flag = .false. 357 | file_exists = .false. 358 | if (present(append)) then 359 | append_flag = append 360 | if (append) inquire(file=filename, exist=file_exists) 361 | end if 362 | 363 | if (append_flag .and. file_exists) then 364 | open(newunit=me%iunit,file=filename,status='OLD',position='APPEND',iostat=istat) 365 | else 366 | open(newunit=me%iunit,file=filename,status='REPLACE',iostat=istat) 367 | end if 368 | 369 | if (istat==0) then 370 | status_ok = .true. 371 | else 372 | if (me%verbose) write(error_unit,'(A)') & 373 | 'Error opening file: '//trim(filename) 374 | status_ok = .false. 375 | end if 376 | 377 | end subroutine open_csv_file 378 | !***************************************************************************************** 379 | 380 | !***************************************************************************************** 381 | !> 382 | ! Close a CSV file after writing 383 | 384 | subroutine close_csv_file(me,status_ok) 385 | 386 | implicit none 387 | 388 | class(csv_file),intent(inout) :: me 389 | logical,intent(out) :: status_ok !! status flag 390 | 391 | integer :: istat !! close `iostat` flag 392 | 393 | close(me%iunit,iostat=istat) 394 | status_ok = istat==0 395 | 396 | end subroutine close_csv_file 397 | !***************************************************************************************** 398 | 399 | !***************************************************************************************** 400 | !> 401 | ! Add a cell to a CSV file. 402 | ! 403 | !@todo Need to check the `istat` values for errors. 404 | 405 | subroutine add_cell(me,val,int_fmt,real_fmt,trim_str) 406 | 407 | implicit none 408 | 409 | class(csv_file),intent(inout) :: me 410 | class(*),intent(in) :: val !! the value to add 411 | character(len=*),intent(in),optional :: int_fmt !! if `val` is an integer, use 412 | !! this format string. 413 | character(len=*),intent(in),optional :: real_fmt !! if `val` is a real, use 414 | !! this format string. 415 | logical,intent(in),optional :: trim_str !! if `val` is a string, then trim it. 416 | 417 | integer :: istat !! write `iostat` flag 418 | character(len=:),allocatable :: ifmt !! actual format string to use for integers 419 | character(len=:),allocatable :: rfmt !! actual format string to use for reals 420 | logical :: trimstr !! if the strings are to be trimmed 421 | character(len=max_real_str_len) :: real_val !! for writing a real value 422 | character(len=max_integer_str_len) :: int_val !! for writing an integer value 423 | 424 | ! make sure the row isn't already finished 425 | if (me%icol 522 | ! Add a vector to a CSV file. Each element is added as a cell to the current line. 523 | 524 | subroutine add_vector(me,val,int_fmt,real_fmt,trim_str) 525 | 526 | implicit none 527 | 528 | class(csv_file),intent(inout) :: me 529 | class(*),dimension(:),intent(in) :: val !! the values to add 530 | character(len=*),intent(in),optional :: int_fmt !! if `val` is an integer, use 531 | !! this format string. 532 | character(len=*),intent(in),optional :: real_fmt !! if `val` is a real, use 533 | !! this format string. 534 | logical,intent(in),optional :: trim_str !! if `val` is a string, then trim it. 535 | 536 | integer :: i !! counter 537 | 538 | do i=1,size(val) 539 | 540 | #if ( defined __GFORTRAN__ ) && ( __GNUC__ <= 10 ) 541 | ! This is a stupid workaround for gfortran bugs (tested with 7.2.0) 542 | select type (val) 543 | type is (character(len=*)) 544 | call me%add(val(i),int_fmt,real_fmt,trim_str) 545 | class default 546 | call me%add(val(i),int_fmt,real_fmt,trim_str) 547 | end select 548 | #else 549 | call me%add(val(i),int_fmt,real_fmt,trim_str) 550 | #endif 551 | 552 | end do 553 | 554 | end subroutine add_vector 555 | !***************************************************************************************** 556 | 557 | !***************************************************************************************** 558 | !> 559 | ! Add a matrix to a CSV file. Each row is added as a new line. 560 | ! Line breaks are added at the end of each line (in this way it 561 | ! differs from the other `add` routines). 562 | 563 | subroutine add_matrix(me,val,int_fmt,real_fmt,trim_str) 564 | 565 | implicit none 566 | 567 | class(csv_file),intent(inout) :: me 568 | class(*),dimension(:,:),intent(in) :: val !! the values to add 569 | character(len=*),intent(in),optional :: int_fmt !! if `val` is an integer, use 570 | !! this format string. 571 | character(len=*),intent(in),optional :: real_fmt !! if `val` is a real, use 572 | !! this format string. 573 | logical,intent(in),optional :: trim_str !! if `val` is a string, then trim it. 574 | 575 | integer :: i !! counter 576 | 577 | ! add each row: 578 | do i=1,size(val,1) 579 | call me%add(val(i,:),int_fmt,real_fmt,trim_str) 580 | call me%next_row() 581 | end do 582 | 583 | end subroutine add_matrix 584 | !***************************************************************************************** 585 | 586 | !***************************************************************************************** 587 | !> 588 | ! Advance to the next row in the CSV file 589 | ! (write any blank cells that are necessary to finish the row) 590 | 591 | subroutine next_row(me) 592 | 593 | implicit none 594 | 595 | class(csv_file),intent(inout) :: me 596 | 597 | integer :: i !! counter 598 | integer :: n !! number of blank cells to write 599 | 600 | if (me%icol>0) then 601 | n = me%n_cols - me%icol 602 | do i=1,n 603 | if (i==n) then !no trailing delimiter 604 | if (me%enclose_strings_in_quotes) then 605 | write(me%iunit,'(A)',advance='NO') me%quote//me%quote 606 | end if 607 | else 608 | if (me%enclose_strings_in_quotes) then 609 | write(me%iunit,'(A)',advance='NO') me%quote//me%quote//me%delimiter 610 | else 611 | write(me%iunit,'(A)',advance='NO') me%delimiter 612 | end if 613 | end if 614 | end do 615 | write(me%iunit,'(A)') '' ! new line 616 | end if 617 | 618 | me%icol = 0 ! this row is finished 619 | 620 | end subroutine next_row 621 | !***************************************************************************************** 622 | 623 | !***************************************************************************************** 624 | !> 625 | ! Returns the header as a `type(csv_string)` array. 626 | ! (`read` must have already been called to read the file). 627 | 628 | subroutine get_header_csv_str(me,header,status_ok) 629 | 630 | implicit none 631 | 632 | class(csv_file),intent(inout) :: me 633 | type(csv_string),dimension(:),allocatable,intent(out) :: header 634 | logical,intent(out) :: status_ok 635 | 636 | integer :: i !! column counter 637 | 638 | if (allocated(me%header)) then 639 | 640 | allocate(header(me%n_cols)) 641 | do i=1,me%n_cols 642 | header(i) = me%header(i) 643 | end do 644 | status_ok = .true. 645 | 646 | else 647 | if (me%verbose) write(error_unit,'(A)') 'Error: no header in class.' 648 | status_ok = .false. 649 | end if 650 | 651 | end subroutine get_header_csv_str 652 | !***************************************************************************************** 653 | 654 | !***************************************************************************************** 655 | !> 656 | ! Returns the header as a `character(len=*)` array. 657 | ! (`read` must have already been called to read the file). 658 | 659 | subroutine get_header_str(me,header,status_ok) 660 | 661 | implicit none 662 | 663 | class(csv_file),intent(inout) :: me 664 | character(len=*),dimension(:),allocatable,intent(out) :: header 665 | logical,intent(out) :: status_ok 666 | 667 | integer :: i !! column counter 668 | 669 | if (allocated(me%header)) then 670 | 671 | allocate(header(me%n_cols)) 672 | do i=1,me%n_cols 673 | header(i) = me%header(i)%str 674 | end do 675 | status_ok = .true. 676 | 677 | else 678 | if (me%verbose) write(error_unit,'(A)') 'Error: no header in class.' 679 | status_ok = .false. 680 | end if 681 | 682 | end subroutine get_header_str 683 | !***************************************************************************************** 684 | 685 | !***************************************************************************************** 686 | !> 687 | ! Returns a `character(len=*)` array containing the csv data 688 | ! (`read` must have already been called to read the file). 689 | 690 | subroutine get_csv_data_as_str(me,csv_data,status_ok) 691 | 692 | implicit none 693 | 694 | class(csv_file),intent(inout) :: me 695 | character(len=*),dimension(:,:),allocatable,intent(out) :: csv_data !! the data 696 | logical,intent(out) :: status_ok !! status flag 697 | 698 | integer :: i !! row counter 699 | integer :: j !! column counter 700 | 701 | if (allocated(me%csv_data)) then 702 | ! size the output array: 703 | allocate(csv_data(me%n_rows,me%n_cols)) 704 | ! convert each element to a string: 705 | do concurrent (j=1:me%n_cols) 706 | do concurrent (i=1:me%n_rows) 707 | csv_data(i,j) = me%csv_data(i,j)%str 708 | end do 709 | end do 710 | status_ok = .true. 711 | else 712 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 713 | status_ok = .false. 714 | end if 715 | 716 | end subroutine get_csv_data_as_str 717 | !***************************************************************************************** 718 | 719 | !***************************************************************************************** 720 | !> 721 | ! Convert a string to a `real(sp)` 722 | 723 | pure elemental subroutine to_real_sp(str,val,status_ok) 724 | 725 | implicit none 726 | 727 | character(len=*),intent(in) :: str 728 | real(sp),intent(out) :: val 729 | logical,intent(out) :: status_ok 730 | 731 | integer :: istat !! read `iostat` error code 732 | 733 | read(str,fmt=*,iostat=istat) val 734 | if (istat==0) then 735 | status_ok = .true. 736 | else 737 | status_ok = .false. 738 | val = zero 739 | end if 740 | 741 | end subroutine to_real_sp 742 | !***************************************************************************************** 743 | 744 | !***************************************************************************************** 745 | !> 746 | ! Convert a string to a `real(wp)` 747 | 748 | pure elemental subroutine to_real_wp(str,val,status_ok) 749 | 750 | implicit none 751 | 752 | character(len=*),intent(in) :: str 753 | real(wp),intent(out) :: val 754 | logical,intent(out) :: status_ok 755 | 756 | integer :: istat !! read `iostat` error code 757 | 758 | read(str,fmt=*,iostat=istat) val 759 | if (istat==0) then 760 | status_ok = .true. 761 | else 762 | status_ok = .false. 763 | val = zero 764 | end if 765 | 766 | end subroutine to_real_wp 767 | !***************************************************************************************** 768 | 769 | !***************************************************************************************** 770 | !> 771 | ! Convert a string to a `real(qp)` 772 | 773 | pure elemental subroutine to_real_qp(str,val,status_ok) 774 | 775 | implicit none 776 | 777 | character(len=*),intent(in) :: str 778 | real(qp),intent(out) :: val 779 | logical,intent(out) :: status_ok 780 | 781 | integer :: istat !! read `iostat` error code 782 | 783 | read(str,fmt=*,iostat=istat) val 784 | if (istat==0) then 785 | status_ok = .true. 786 | else 787 | status_ok = .false. 788 | val = zero 789 | end if 790 | 791 | end subroutine to_real_qp 792 | !***************************************************************************************** 793 | 794 | !***************************************************************************************** 795 | !> 796 | ! Convert a string to a `integer(ip)` 797 | 798 | pure elemental subroutine to_integer(str,val,status_ok) 799 | 800 | implicit none 801 | 802 | character(len=*),intent(in) :: str 803 | integer(ip),intent(out) :: val 804 | logical,intent(out) :: status_ok 805 | 806 | integer :: istat !! read `iostat` error code 807 | 808 | read(str,fmt=default_int_fmt,iostat=istat) val 809 | if (istat==0) then 810 | status_ok = .true. 811 | else 812 | status_ok = .false. 813 | val = 0 814 | end if 815 | 816 | end subroutine to_integer 817 | !***************************************************************************************** 818 | 819 | !***************************************************************************************** 820 | !> 821 | ! Convert a string to a `logical` 822 | ! 823 | ! * Evaluates to `.true.` for strings ['1','t','true','.true.'] 824 | ! * Evaluates to `.false.` for strings ['0','f','false','.false.'] 825 | ! 826 | ! The string match is not case sensitive. 827 | 828 | pure elemental subroutine to_logical(str,val,status_ok) 829 | 830 | implicit none 831 | 832 | character(len=*),intent(in) :: str 833 | logical,intent(out) :: val 834 | logical,intent(out) :: status_ok 835 | 836 | character(len=:),allocatable :: tmp 837 | 838 | ! True and False options (all lowercase): 839 | character(len=*),dimension(4),parameter :: true_str = ['1 ',& 840 | 't ',& 841 | 'true ',& 842 | '.true.'] 843 | character(len=*),dimension(4),parameter :: false_str = ['0 ',& 844 | 'f ',& 845 | 'false ',& 846 | '.false.'] 847 | 848 | tmp = lowercase_string(str) 849 | if ( any(tmp==true_str) ) then 850 | val = .true. 851 | status_ok = .true. 852 | else if ( any(tmp==false_str) ) then 853 | val = .false. 854 | status_ok = .true. 855 | else 856 | val = .false. 857 | status_ok = .false. 858 | end if 859 | 860 | end subroutine to_logical 861 | !***************************************************************************************** 862 | 863 | !***************************************************************************************** 864 | !> 865 | ! Returns an array indicating the variable type of each columns. 866 | ! 867 | !@note The first element in the column is used to determine the type. 868 | 869 | subroutine variable_types(me,itypes,status_ok) 870 | 871 | implicit none 872 | 873 | class(csv_file),intent(inout) :: me 874 | integer,dimension(:),allocatable,intent(out) :: itypes 875 | logical,intent(out) :: status_ok 876 | 877 | integer :: i !! counter 878 | 879 | if (allocated(me%csv_data)) then 880 | allocate(itypes(me%n_cols)) 881 | do i=1,me%n_cols 882 | call infer_variable_type(me%csv_data(1,i)%str,itypes(i)) 883 | end do 884 | status_ok = .true. 885 | else 886 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 887 | status_ok = .false. 888 | end if 889 | 890 | end subroutine variable_types 891 | !***************************************************************************************** 892 | 893 | !***************************************************************************************** 894 | !> 895 | ! Infers the variable type, assuming the following precedence: 896 | ! 897 | ! * integer 898 | ! * double 899 | ! * logical 900 | ! * character 901 | 902 | subroutine infer_variable_type(str,itype) 903 | 904 | implicit none 905 | 906 | character(len=*),intent(in) :: str 907 | integer,intent(out) :: itype 908 | 909 | real(wp) :: rval !! a real value 910 | integer(ip) :: ival !! an iteger value 911 | logical :: lval !! a logical value 912 | logical :: status_ok !! status flag 913 | 914 | call to_integer(str,ival,status_ok) 915 | if (status_ok) then 916 | itype = csv_type_integer 917 | return 918 | end if 919 | 920 | call to_real_wp(str,rval,status_ok) 921 | if (status_ok) then 922 | itype = csv_type_double 923 | return 924 | end if 925 | 926 | call to_logical(str,lval,status_ok) 927 | if (status_ok) then 928 | itype = csv_type_logical 929 | return 930 | end if 931 | 932 | ! default is string: 933 | itype = csv_type_string 934 | 935 | end subroutine infer_variable_type 936 | !***************************************************************************************** 937 | 938 | !***************************************************************************************** 939 | !> 940 | ! Get an individual value from the `csv_data` structure in the CSV class. 941 | ! 942 | ! The output `val` can be an `integer(ip)`, `real(wp)`, 943 | ! `logical`, or `character(len=*)` variable. 944 | 945 | subroutine csv_get_value(me,row,col,val,status_ok) 946 | 947 | implicit none 948 | 949 | class(csv_file),intent(inout) :: me 950 | integer,intent(in) :: row !! row number 951 | integer,intent(in) :: col !! column number 952 | class(*),intent(out) :: val !! the returned value 953 | logical,intent(out) :: status_ok !! status flag 954 | 955 | select type (val) 956 | type is (integer(ip)) 957 | call to_integer(me%csv_data(row,col)%str,val,status_ok) 958 | type is (real(sp)) 959 | call to_real_sp(me%csv_data(row,col)%str,val,status_ok) 960 | type is (real(wp)) 961 | call to_real_wp(me%csv_data(row,col)%str,val,status_ok) 962 | type is (real(qp)) 963 | call to_real_qp(me%csv_data(row,col)%str,val,status_ok) 964 | type is (logical) 965 | call to_logical(me%csv_data(row,col)%str,val,status_ok) 966 | type is (character(len=*)) 967 | status_ok = .true. 968 | if (allocated(me%csv_data(row,col)%str)) then 969 | val = me%csv_data(row,col)%str 970 | else 971 | val = '' 972 | end if 973 | type is (csv_string) 974 | status_ok = .true. 975 | val = me%csv_data(row,col) 976 | class default 977 | status_ok = .false. 978 | end select 979 | 980 | end subroutine csv_get_value 981 | !***************************************************************************************** 982 | 983 | !***************************************************************************************** 984 | !> 985 | ! Return a column from a CSV file vector. 986 | ! 987 | !@note This routine requires that the `r` array already be allocated. 988 | ! This is because Fortran doesn't want to allow to you pass 989 | ! a non-polymorphic variable into a routine with a dummy variable 990 | ! with `class(*),dimension(:),allocatable,intent(out)` attributes. 991 | 992 | subroutine get_column(me,icol,r,status_ok) 993 | 994 | implicit none 995 | 996 | class(csv_file),intent(inout) :: me 997 | integer,intent(in) :: icol !! column number 998 | class(*),dimension(:),intent(out) :: r !! assumed to have been allocated to 999 | !! the correct size by the caller. 1000 | !! (`n_rows`) 1001 | logical,intent(out) :: status_ok !! status flag 1002 | 1003 | integer :: i !! counter 1004 | character(len=:),allocatable :: tmp !! for gfortran workaround 1005 | 1006 | ! we know the data is allocated, since that 1007 | ! was checked by the calling routines. 1008 | 1009 | if (me%n_cols>=icol .and. icol>0) then 1010 | 1011 | do i=1,me%n_rows ! row loop 1012 | 1013 | #if ( defined __GFORTRAN__ ) && ( __GNUC__ <= 10 ) 1014 | ! the following is a workaround for gfortran bugs: 1015 | select type (r) 1016 | type is (character(len=*)) 1017 | tmp = repeat(' ',len(r)) ! size the string 1018 | call me%csv_get_value(i,icol,tmp,status_ok) 1019 | r(i) = tmp 1020 | class default 1021 | call me%csv_get_value(i,icol,r(i),status_ok) 1022 | end select 1023 | #else 1024 | call me%csv_get_value(i,icol,r(i),status_ok) 1025 | #endif 1026 | if (.not. status_ok) then 1027 | select type (r) 1028 | ! note: character conversion can never fail, so not 1029 | ! checking for that here. also we know it is real, 1030 | ! integer, or logical at this point. 1031 | type is (integer(ip)) 1032 | if (me%verbose) write(error_unit,'(A)') & 1033 | 'Error converting string to integer: '//trim(me%csv_data(i,icol)%str) 1034 | r(i) = 0 1035 | type is (real(sp)) 1036 | if (me%verbose) write(error_unit,'(A)') & 1037 | 'Error converting string to real(real32): '//trim(me%csv_data(i,icol)%str) 1038 | r(i) = zero 1039 | type is (real(wp)) 1040 | if (me%verbose) write(error_unit,'(A)') & 1041 | 'Error converting string to real(real64): '//trim(me%csv_data(i,icol)%str) 1042 | r(i) = zero 1043 | type is (real(qp)) 1044 | if (me%verbose) write(error_unit,'(A)') & 1045 | 'Error converting string to real(real128): '//trim(me%csv_data(i,icol)%str) 1046 | r(i) = zero 1047 | type is (logical) 1048 | if (me%verbose) write(error_unit,'(A)') & 1049 | 'Error converting string to logical: '//trim(me%csv_data(i,icol)%str) 1050 | r(i) = .false. 1051 | end select 1052 | end if 1053 | 1054 | end do 1055 | 1056 | else 1057 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: invalid column number: ',icol 1058 | status_ok = .false. 1059 | end if 1060 | 1061 | end subroutine get_column 1062 | !***************************************************************************************** 1063 | 1064 | !***************************************************************************************** 1065 | !> 1066 | ! Return a column from a CSV file as a `real(sp)` vector. 1067 | 1068 | subroutine get_real_sp_column(me,icol,r,status_ok) 1069 | 1070 | implicit none 1071 | 1072 | class(csv_file),intent(inout) :: me 1073 | integer,intent(in) :: icol !! column number 1074 | real(sp),dimension(:),allocatable,intent(out) :: r 1075 | logical,intent(out) :: status_ok 1076 | 1077 | if (allocated(me%csv_data)) then 1078 | allocate(r(me%n_rows)) ! size the output vector 1079 | call me%get_column(icol,r,status_ok) 1080 | else 1081 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1082 | status_ok = .false. 1083 | end if 1084 | 1085 | end subroutine get_real_sp_column 1086 | !***************************************************************************************** 1087 | 1088 | !***************************************************************************************** 1089 | !> 1090 | ! Return a column from a CSV file as a `real(wp)` vector. 1091 | 1092 | subroutine get_real_wp_column(me,icol,r,status_ok) 1093 | 1094 | implicit none 1095 | 1096 | class(csv_file),intent(inout) :: me 1097 | integer,intent(in) :: icol !! column number 1098 | real(wp),dimension(:),allocatable,intent(out) :: r 1099 | logical,intent(out) :: status_ok 1100 | 1101 | if (allocated(me%csv_data)) then 1102 | allocate(r(me%n_rows)) ! size the output vector 1103 | call me%get_column(icol,r,status_ok) 1104 | else 1105 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1106 | status_ok = .false. 1107 | end if 1108 | 1109 | end subroutine get_real_wp_column 1110 | !***************************************************************************************** 1111 | 1112 | !***************************************************************************************** 1113 | !> 1114 | ! Return a column from a CSV file as a `real(qp)` vector. 1115 | 1116 | subroutine get_real_qp_column(me,icol,r,status_ok) 1117 | 1118 | implicit none 1119 | 1120 | class(csv_file),intent(inout) :: me 1121 | integer,intent(in) :: icol !! column number 1122 | real(qp),dimension(:),allocatable,intent(out) :: r 1123 | logical,intent(out) :: status_ok 1124 | 1125 | if (allocated(me%csv_data)) then 1126 | allocate(r(me%n_rows)) ! size the output vector 1127 | call me%get_column(icol,r,status_ok) 1128 | else 1129 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1130 | status_ok = .false. 1131 | end if 1132 | 1133 | end subroutine get_real_qp_column 1134 | !***************************************************************************************** 1135 | 1136 | !***************************************************************************************** 1137 | !> 1138 | ! Return a column from a CSV file as a `integer(ip)` vector. 1139 | 1140 | subroutine get_integer_column(me,icol,r,status_ok) 1141 | 1142 | implicit none 1143 | 1144 | class(csv_file),intent(inout) :: me 1145 | integer,intent(in) :: icol !! column number 1146 | integer(ip),dimension(:),allocatable,intent(out) :: r 1147 | logical,intent(out) :: status_ok 1148 | 1149 | if (allocated(me%csv_data)) then 1150 | allocate(r(me%n_rows)) ! size the output vector 1151 | call me%get_column(icol,r,status_ok) 1152 | else 1153 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1154 | status_ok = .false. 1155 | end if 1156 | 1157 | end subroutine get_integer_column 1158 | !***************************************************************************************** 1159 | 1160 | !***************************************************************************************** 1161 | !> 1162 | ! Convert a column from a `csv_string` matrix to a `logical` vector. 1163 | 1164 | subroutine get_logical_column(me,icol,r,status_ok) 1165 | 1166 | implicit none 1167 | 1168 | class(csv_file),intent(inout) :: me 1169 | integer,intent(in) :: icol !! column number 1170 | logical,dimension(:),allocatable,intent(out) :: r 1171 | logical,intent(out) :: status_ok 1172 | 1173 | if (allocated(me%csv_data)) then 1174 | allocate(r(me%n_rows)) ! size the output vector 1175 | call me%get_column(icol,r,status_ok) 1176 | else 1177 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1178 | status_ok = .false. 1179 | end if 1180 | 1181 | end subroutine get_logical_column 1182 | !***************************************************************************************** 1183 | 1184 | !***************************************************************************************** 1185 | !> 1186 | ! Convert a column from a `csv_string` matrix to a `character(len=*)` vector. 1187 | 1188 | subroutine get_character_column(me,icol,r,status_ok) 1189 | 1190 | implicit none 1191 | 1192 | class(csv_file),intent(inout) :: me 1193 | integer,intent(in) :: icol !! column number 1194 | character(len=*),dimension(:),allocatable,intent(out) :: r 1195 | logical,intent(out) :: status_ok 1196 | 1197 | if (allocated(me%csv_data)) then 1198 | allocate(r(me%n_rows)) ! size the output vector 1199 | call me%get_column(icol,r,status_ok) 1200 | else 1201 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1202 | status_ok = .false. 1203 | end if 1204 | 1205 | end subroutine get_character_column 1206 | !***************************************************************************************** 1207 | 1208 | !***************************************************************************************** 1209 | !> 1210 | ! Convert a column from a `csv_string` matrix to a `type(csv_string)` vector. 1211 | 1212 | subroutine get_csv_string_column(me,icol,r,status_ok) 1213 | 1214 | implicit none 1215 | 1216 | class(csv_file),intent(inout) :: me 1217 | integer,intent(in) :: icol !! column number 1218 | type(csv_string),dimension(:),allocatable,intent(out) :: r 1219 | logical,intent(out) :: status_ok 1220 | 1221 | if (allocated(me%csv_data)) then 1222 | allocate(r(me%n_rows)) ! size the output vector 1223 | call me%get_column(icol,r,status_ok) 1224 | else 1225 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Error: class has not been initialized' 1226 | status_ok = .false. 1227 | end if 1228 | 1229 | end subroutine get_csv_string_column 1230 | !***************************************************************************************** 1231 | 1232 | !***************************************************************************************** 1233 | !> 1234 | ! Tokenize a line from a CSV file. The result is an array of `csv_string` types. 1235 | ! 1236 | !### Notes 1237 | ! * Quotes are removed if the entire cell is contained in quotes. 1238 | ! 1239 | !@warning It does not account for delimiters in quotes 1240 | ! (these are treated as a new cell). Need to fix! 1241 | 1242 | subroutine tokenize_csv_line(me,line,cells) 1243 | 1244 | implicit none 1245 | 1246 | class(csv_file),intent(inout) :: me 1247 | character(len=*),intent(in) :: line 1248 | type(csv_string),dimension(:),allocatable,intent(out) :: cells 1249 | 1250 | integer :: i !! counter 1251 | character(len=:),allocatable :: tmp !! a temp string with whitespace removed 1252 | integer :: n !! length of compressed string 1253 | 1254 | call split(line,me%delimiter,me%chunk_size,cells) 1255 | 1256 | ! remove quotes if present: 1257 | do i = 1, size(cells) 1258 | 1259 | ! remove whitespace from the string: 1260 | tmp = trim(adjustl(cells(i)%str)) 1261 | n = len(tmp) 1262 | 1263 | if (n>1) then 1264 | ! if the first and last non-blank character is 1265 | ! a quote, then remove them and replace with what 1266 | ! is inside the quotes. Otherwise, leave it as is. 1267 | if (tmp(1:1)==me%quote .and. tmp(n:n)==me%quote) then 1268 | if (n>2) then 1269 | cells(i)%str = tmp(2:n-1) ! remove the quotes 1270 | else 1271 | cells(i)%str = '' ! empty string 1272 | end if 1273 | end if 1274 | end if 1275 | 1276 | end do 1277 | 1278 | end subroutine tokenize_csv_line 1279 | !***************************************************************************************** 1280 | 1281 | !***************************************************************************************** 1282 | !> 1283 | ! Returns the number of lines in a text file. 1284 | ! 1285 | !@note It rewinds the file back to the beginning when finished. 1286 | 1287 | function number_of_lines_in_file(iunit) result(n_lines) 1288 | 1289 | implicit none 1290 | 1291 | integer,intent(in) :: iunit !! the file unit number 1292 | !! (assumed to be open) 1293 | integer :: n_lines !! the number of lines in the file 1294 | 1295 | character(len=1) :: tmp 1296 | integer :: istat 1297 | 1298 | rewind(iunit) 1299 | n_lines = 0 1300 | do 1301 | read(iunit,fmt='(A1)',iostat=istat) tmp 1302 | if (is_iostat_end(istat)) exit 1303 | n_lines = n_lines + 1 1304 | end do 1305 | rewind(iunit) 1306 | 1307 | end function number_of_lines_in_file 1308 | !***************************************************************************************** 1309 | 1310 | !***************************************************************************************** 1311 | !> 1312 | ! Reads the next line from a file. 1313 | 1314 | subroutine read_line_from_file(me,iunit,line,status_ok) 1315 | 1316 | implicit none 1317 | 1318 | class(csv_file),intent(in) :: me 1319 | integer,intent(in) :: iunit 1320 | character(len=:),allocatable,intent(out) :: line 1321 | logical,intent(out) :: status_ok !! true if no problems 1322 | 1323 | integer :: nread !! character count specifier for read statement 1324 | integer :: istat !! file read io status flag 1325 | character(len=me%chunk_size) :: buffer !! the file read buffer 1326 | 1327 | nread = 0 1328 | buffer = '' 1329 | line = '' 1330 | status_ok = .true. 1331 | 1332 | do 1333 | ! read in the next block of text from the line: 1334 | read(iunit,fmt='(A)',advance='NO',size=nread,iostat=istat) buffer 1335 | if (IS_IOSTAT_END(istat) .or. IS_IOSTAT_EOR(istat)) then 1336 | ! add the last block of text before the end of record 1337 | if (nread>0) line = line//buffer(1:nread) 1338 | exit 1339 | else if (istat==0) then ! all the characters were read 1340 | line = line//buffer ! add this block of text to the string 1341 | else ! some kind of error 1342 | if (me%verbose) write(error_unit,'(A,1X,I5)') 'Read error for file unit: ',iunit 1343 | status_ok = .false. 1344 | exit 1345 | end if 1346 | end do 1347 | 1348 | end subroutine read_line_from_file 1349 | !***************************************************************************************** 1350 | 1351 | !***************************************************************************************** 1352 | !> 1353 | ! Split a character string using a token. 1354 | ! This routine is inspired by the Python split function. 1355 | ! 1356 | !### Example 1357 | !````Fortran 1358 | ! character(len=:),allocatable :: s 1359 | ! type(csv_string),dimension(:),allocatable :: vals 1360 | ! s = '1,2,3,4,5' 1361 | ! call split(s,',',vals) 1362 | !```` 1363 | ! 1364 | !@warning Does not account for tokens contained within quotes string !!! 1365 | 1366 | pure subroutine split(str,token,chunk_size,vals) 1367 | 1368 | implicit none 1369 | 1370 | character(len=*),intent(in) :: str 1371 | character(len=*),intent(in) :: token 1372 | integer,intent(in) :: chunk_size !! for expanding vectors 1373 | type(csv_string),dimension(:),allocatable,intent(out) :: vals 1374 | 1375 | integer :: i !! counter 1376 | integer :: len_str !! significant length of `str` 1377 | integer :: len_token !! length of the token 1378 | integer :: n_tokens !! number of tokens 1379 | integer :: i1 !! index 1380 | integer :: i2 !! index 1381 | integer :: j !! counters 1382 | integer,dimension(:),allocatable :: itokens !! start indices of the 1383 | !! token locations in `str` 1384 | 1385 | len_token = len(token) ! length of the token 1386 | n_tokens = 0 ! initialize the token counter 1387 | j = 0 ! index to start looking for the next token 1388 | 1389 | ! first, count the number of times the token 1390 | ! appears in the string, and get the token indices. 1391 | ! 1392 | ! Examples: 1393 | ! ', ' --> 1 1394 | ! '1234,67,90' --> 5,8 1395 | ! '123, ' --> 4 1396 | 1397 | ! length of the string 1398 | if (token == ' ') then 1399 | ! in this case, we can't ignore trailing space 1400 | len_str = len(str) 1401 | else 1402 | ! safe to ignore trailing space when looking for tokens 1403 | len_str = len_trim(str) 1404 | end if 1405 | 1406 | j = 1 1407 | n_tokens = 0 1408 | do 1409 | if (j>len_str) exit ! end of string, finished 1410 | i = index(str(j:),token) ! index of next token in remaining string 1411 | if (i<=0) exit ! no more tokens found 1412 | call expand_vector(itokens,n_tokens,chunk_size,i+j-1) ! save the token location 1413 | j = j + i + (len_token - 1) 1414 | end do 1415 | call expand_vector(itokens,n_tokens,chunk_size,finished=.true.) ! resize the vector 1416 | 1417 | allocate(vals(n_tokens+1)) 1418 | 1419 | if (n_tokens>0) then 1420 | 1421 | len_str = len(str) 1422 | 1423 | i1 = 1 1424 | i2 = itokens(1)-1 1425 | if (i2>=i1) then 1426 | vals(1)%str = str(i1:i2) 1427 | else 1428 | vals(1)%str = '' !the first character is a token 1429 | end if 1430 | 1431 | ! 1 2 3 1432 | ! 'a,b,c,d' 1433 | 1434 | do i=2,n_tokens 1435 | i1 = itokens(i-1)+len_token 1436 | i2 = itokens(i)-1 1437 | if (i2>=i1) then 1438 | vals(i)%str = str(i1:i2) 1439 | else 1440 | vals(i)%str = '' !empty element (e.g., 'abc,,def') 1441 | end if 1442 | end do 1443 | 1444 | i1 = itokens(n_tokens) + len_token 1445 | i2 = len_str 1446 | if (itokens(n_tokens)+len_token<=len_str) then 1447 | vals(n_tokens+1)%str = str(i1:i2) 1448 | else 1449 | vals(n_tokens+1)%str = '' !the last character was a token 1450 | end if 1451 | 1452 | else 1453 | !no tokens present, so just return the original string: 1454 | vals(1)%str = str 1455 | end if 1456 | 1457 | end subroutine split 1458 | !***************************************************************************************** 1459 | 1460 | !***************************************************************************************** 1461 | end module csv_module 1462 | !***************************************************************************************** 1463 | -------------------------------------------------------------------------------- /src/csv_parameters.f90: -------------------------------------------------------------------------------- 1 | !******************************************************************************* 2 | !> 3 | ! Various parameters. 4 | 5 | module csv_parameters 6 | 7 | use csv_kinds 8 | 9 | private 10 | 11 | integer(ip),parameter,public :: max_real_str_len = 256 !! maximum string length of a real number 12 | 13 | character(len=*),parameter,public :: default_sp_fmt = '(E17.8E3)' !! default single number format statement 14 | character(len=*),parameter,public :: default_wp_fmt = '(E27.17E4)' !! default double number format statement 15 | character(len=*),parameter,public :: default_qp_fmt = '(E46.35E5)' !! default quad number format statement 16 | 17 | integer(ip),parameter,public :: max_integer_str_len = 256 !! maximum string length of an integer. 18 | character(len=*),parameter,public :: default_int_fmt = '(I256)' 19 | !! default integer number format statement (for writing integer values to strings and files). 20 | 21 | end module csv_parameters 22 | !******************************************************************************* 23 | -------------------------------------------------------------------------------- /src/csv_utilities.f90: -------------------------------------------------------------------------------- 1 | !******************************************************************************* 2 | !> author: Jacob Williams 3 | ! 4 | ! Utility routines. 5 | 6 | module csv_utilities 7 | 8 | use csv_kinds 9 | use csv_parameters 10 | 11 | private 12 | 13 | integer,parameter :: max_size_for_insertion_sort = 20 !! max size for using insertion sort. 14 | 15 | character(len=*),parameter :: upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' !! uppercase characters 16 | character(len=*),parameter :: lower = 'abcdefghijklmnopqrstuvwxyz' !! lowercase characters 17 | 18 | public :: unique 19 | public :: expand_vector 20 | public :: sort_ascending 21 | public :: lowercase_string 22 | 23 | contains 24 | !******************************************************************************* 25 | 26 | !******************************************************************************* 27 | !> 28 | ! Add elements to the integer vector in chunks. 29 | 30 | pure subroutine expand_vector(vec,n,chunk_size,val,finished) 31 | 32 | implicit none 33 | 34 | integer,dimension(:),allocatable,intent(inout) :: vec 35 | integer,intent(inout) :: n !! counter for last element added to `vec`. 36 | !! must be initialized to `size(vec)` 37 | !! (or 0 if not allocated) before first call 38 | integer,intent(in) :: chunk_size !! allocate `vec` in blocks of this size (>0) 39 | integer,intent(in),optional :: val !! the value to add to `vec` 40 | logical,intent(in),optional :: finished !! set to true to return `vec` 41 | !! as its correct size (`n`) 42 | 43 | integer,dimension(:),allocatable :: tmp !! temporary array 44 | 45 | if (present(val)) then 46 | if (allocated(vec)) then 47 | if (n==size(vec)) then 48 | ! have to add another chunk: 49 | allocate(tmp(size(vec)+chunk_size)) 50 | tmp(1:size(vec)) = vec 51 | call move_alloc(tmp,vec) 52 | end if 53 | n = n + 1 54 | else 55 | ! the first element: 56 | allocate(vec(chunk_size)) 57 | n = 1 58 | end if 59 | vec(n) = val 60 | end if 61 | 62 | if (present(finished)) then 63 | if (finished) then 64 | ! set vec to actual size (n): 65 | if (allocated(tmp)) deallocate(tmp) 66 | allocate(tmp(n)) 67 | tmp = vec(1:n) 68 | call move_alloc(tmp,vec) 69 | end if 70 | end if 71 | 72 | end subroutine expand_vector 73 | !******************************************************************************* 74 | 75 | !******************************************************************************* 76 | !> 77 | ! Returns only the unique elements of the vector. 78 | 79 | function unique(vec,chunk_size) result(ivec_unique) 80 | 81 | implicit none 82 | 83 | integer,dimension(:),intent(in) :: vec !! a vector of integers 84 | integer,intent(in) :: chunk_size !! chunk size for adding to arrays 85 | integer,dimension(:),allocatable :: ivec_unique !! unique elements of `ivec` 86 | 87 | integer,dimension(size(vec)) :: ivec !! temp copy of vec 88 | integer :: i !! counter 89 | integer :: n !! number of unique elements 90 | 91 | ! first we sort it: 92 | ivec = vec ! make a copy 93 | call sort_ascending(ivec) 94 | 95 | ! add the first element: 96 | n = 1 97 | ivec_unique = [ivec(1)] 98 | 99 | ! walk through array and get the unique ones: 100 | if (size(ivec)>1) then 101 | do i = 2, size(ivec) 102 | if (ivec(i)/=ivec(i-1)) then 103 | call expand_vector(ivec_unique,n,chunk_size,val=ivec(i)) 104 | end if 105 | end do 106 | call expand_vector(ivec_unique,n,chunk_size,finished=.true.) 107 | end if 108 | 109 | end function unique 110 | !******************************************************************************* 111 | 112 | !******************************************************************************* 113 | !> 114 | ! Sorts an integer array `ivec` in increasing order. 115 | ! Uses a basic recursive quicksort 116 | ! (with insertion sort for partitions with \(\le\) 20 elements). 117 | 118 | subroutine sort_ascending(ivec) 119 | 120 | implicit none 121 | 122 | integer,dimension(:),intent(inout) :: ivec 123 | 124 | call quicksort(1,size(ivec)) 125 | 126 | contains 127 | 128 | recursive subroutine quicksort(ilow,ihigh) 129 | 130 | !! Sort the array 131 | 132 | implicit none 133 | 134 | integer,intent(in) :: ilow 135 | integer,intent(in) :: ihigh 136 | 137 | integer :: ipivot !! pivot element 138 | integer :: i !! counter 139 | integer :: j !! counter 140 | 141 | if ( ihigh-ilow<=max_size_for_insertion_sort .and. ihigh>ilow ) then 142 | 143 | ! do insertion sort: 144 | do i = ilow + 1,ihigh 145 | do j = i,ilow + 1,-1 146 | if ( ivec(j) < ivec(j-1) ) then 147 | call swap(ivec(j),ivec(j-1)) 148 | else 149 | exit 150 | end if 151 | end do 152 | end do 153 | 154 | else if ( ihigh-ilow>max_size_for_insertion_sort ) then 155 | 156 | ! do the normal quicksort: 157 | call partition(ilow,ihigh,ipivot) 158 | call quicksort(ilow,ipivot - 1) 159 | call quicksort(ipivot + 1,ihigh) 160 | 161 | end if 162 | 163 | end subroutine quicksort 164 | 165 | subroutine partition(ilow,ihigh,ipivot) 166 | 167 | !! Partition the array, based on the 168 | !! lexical ivecing comparison. 169 | 170 | implicit none 171 | 172 | integer,intent(in) :: ilow 173 | integer,intent(in) :: ihigh 174 | integer,intent(out) :: ipivot 175 | 176 | integer :: i,ip 177 | 178 | call swap(ivec(ilow),ivec((ilow+ihigh)/2)) 179 | ip = ilow 180 | do i = ilow + 1, ihigh 181 | if ( ivec(i) < ivec(ilow) ) then 182 | ip = ip + 1 183 | call swap(ivec(ip),ivec(i)) 184 | end if 185 | end do 186 | call swap(ivec(ilow),ivec(ip)) 187 | ipivot = ip 188 | 189 | end subroutine partition 190 | 191 | end subroutine sort_ascending 192 | !******************************************************************************* 193 | 194 | !******************************************************************************* 195 | !> 196 | ! Swap two integer values. 197 | 198 | pure elemental subroutine swap(i1,i2) 199 | 200 | implicit none 201 | 202 | integer,intent(inout) :: i1 203 | integer,intent(inout) :: i2 204 | 205 | integer :: tmp 206 | 207 | tmp = i1 208 | i1 = i2 209 | i2 = tmp 210 | 211 | end subroutine swap 212 | !******************************************************************************* 213 | 214 | !******************************************************************************* 215 | !> 216 | ! Returns lowercase version of the string. 217 | 218 | pure function lowercase_string(str) result(s_lower) 219 | 220 | implicit none 221 | 222 | character(len=*),intent(in) :: str !! input string 223 | character(len=(len(str))) :: s_lower !! lowercase version of the string 224 | 225 | integer :: i !! counter 226 | integer :: j !! index of uppercase character 227 | 228 | s_lower = str 229 | 230 | do i = 1, len_trim(str) 231 | j = index(upper,s_lower(i:i)) 232 | if (j>0) s_lower(i:i) = lower(j:j) 233 | end do 234 | 235 | end function lowercase_string 236 | !******************************************************************************* 237 | 238 | !******************************************************************************* 239 | end module csv_utilities 240 | !******************************************************************************* 241 | -------------------------------------------------------------------------------- /test/csv_test.f90: -------------------------------------------------------------------------------- 1 | !***************************************************************************************** 2 | !> author: Jacob Williams 3 | ! license: BSD 4 | ! 5 | ! Test of reading and writing CSV files. 6 | 7 | program csv_test 8 | 9 | use csv_module 10 | use iso_fortran_env, only: wp => real64, sp => real32, qp => real128 11 | 12 | implicit none 13 | 14 | call csv_test_1() 15 | call csv_write_test() 16 | call csv_read_test() 17 | 18 | contains 19 | 20 | subroutine csv_test_1() 21 | 22 | implicit none 23 | 24 | type(csv_file) :: f 25 | type(csv_file) :: f2 26 | integer :: i !! counter 27 | integer :: k !! counter 28 | character(len=30),dimension(:),allocatable :: header !! the header 29 | character(len=30),dimension(:,:),allocatable :: csv_data !! the data from the file as strings 30 | real(wp),dimension(:),allocatable :: x !! for getting a real(wp) vector from a csv file 31 | real(sp),dimension(:),allocatable :: y !! for getting a real(sp) vector from a csv file 32 | real(qp),dimension(:),allocatable :: z !! for getting a real(qp) vector from a csv file 33 | logical :: status_ok !! error flag 34 | integer,dimension(:),allocatable :: itypes !! array of variable types in the file 35 | integer :: ifile !! file counter 36 | character(len=30),dimension(:),allocatable :: names 37 | character(len=:),allocatable :: file 38 | 39 | character(len=*),dimension(2),parameter :: files_to_test = ['test.csv ',& 40 | 'test_2_columns.csv'] 41 | character(len=*),dimension(4),parameter :: dirs_to_try = ['../files/', & 42 | './files/ ', & 43 | './ ', & 44 | ' '] 45 | 46 | write(*,*) '' 47 | write(*,*) '============================' 48 | write(*,*) ' csv_test_1 ' 49 | write(*,*) '============================' 50 | write(*,*) '' 51 | 52 | do ifile = 1, size(files_to_test) 53 | 54 | ! a hack to get it to work with all the different build systems 55 | ! no matter the working directory 56 | do k = 1, size(dirs_to_try) 57 | file = trim(dirs_to_try(k))//trim(files_to_test(ifile)) 58 | if (file_exists(file)) exit ! found it 59 | end do 60 | write(*,*) 'read file: '//trim(file) 61 | 62 | ! read the file: 63 | if (ifile==1) then 64 | call f%read(file,header_row=1,status_ok=status_ok) 65 | else 66 | ! also skip a row 67 | call f%read(file,header_row=1,skip_rows=[2],status_ok=status_ok) 68 | end if 69 | 70 | if (.not. status_ok) then 71 | error stop 'could not open file' 72 | end if 73 | 74 | write(*,*) '' 75 | write(*,*) 'File: '//trim(files_to_test(ifile)) 76 | ! print the header and type info: 77 | call f%get_header(header,status_ok) 78 | call f%variable_types(itypes,status_ok) 79 | write(*,*) '' 80 | write(*,'(*(A30,1X,A4))') 'Header', 'Type' 81 | do i=1,size(header) 82 | write(*,'(*(A30,1X,I4))') header(i), itypes(i) 83 | end do 84 | 85 | write(*,*) '' 86 | write(*,*) 'print all the rows:' 87 | 88 | call f%get(csv_data,status_ok) 89 | do i=1,size(csv_data,1) 90 | write(*,'(*(A30,1X))') csv_data(i,:) 91 | end do 92 | 93 | write(*,*) '' 94 | write(*,*) 'get some vectors:' 95 | if (ifile==1) then 96 | write(*,*) '' 97 | write(*,*) 'get real(wp) vector:' 98 | write(*,*) 'age:' 99 | call f%get(3,x,status_ok) 100 | write(*,'(F6.3,1x)',advance='NO') x 101 | write(*,*) '' 102 | write(*,*) 'get real(sp) vector:' 103 | write(*,*) 'age:' 104 | call f%get(3,y,status_ok) 105 | write(*,'(F6.3,1x)',advance='NO') y 106 | write(*,*) '' 107 | 108 | call f%get(3,z,status_ok) ! also try as quad precision 109 | 110 | else 111 | write(*,*) '' 112 | write(*,*) 'name:' 113 | call f%get(2,names,status_ok) 114 | write(*,'(A10,1x)',advance='NO') names 115 | write(*,*) '' 116 | end if 117 | 118 | end do 119 | 120 | ! now test creating a CSV: 121 | call f2%initialize(enclose_strings_in_quotes=.false.,verbose=.true.) 122 | call f2%open('test2.csv',n_cols=4,status_ok=status_ok) 123 | if (status_ok) then 124 | call f2%add(['x','y','z','t']) ! add header as vector 125 | call f2%next_row() 126 | call f2%add(1.0_wp) ! add as scalars 127 | call f2%add(2.0_wp) 128 | call f2%add(3.0_wp) 129 | call f2%add(.true.) 130 | call f2%next_row() 131 | call f2%add([4.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') ! add as vectors 132 | call f2%add(.false.) 133 | call f2%next_row() 134 | call f2%add(1.5_sp) ! add as scalars 135 | call f2%add(2.5_sp) 136 | call f2%add(3.5_sp) 137 | call f2%add(.true.) 138 | call f2%next_row() 139 | call f2%add([4.5_sp,5.5_sp,6.5_sp],real_fmt='(F5.3)') ! add as vectors 140 | call f2%add(.false.) 141 | end if 142 | call f2%close(status_ok) 143 | 144 | end subroutine csv_test_1 145 | 146 | subroutine csv_write_test() 147 | 148 | implicit none 149 | 150 | type(csv_file) :: f 151 | logical :: status_ok 152 | 153 | write(*,*) '' 154 | write(*,*) '============================' 155 | write(*,*) ' csv_write_test ' 156 | write(*,*) '============================' 157 | write(*,*) '' 158 | 159 | ! open the file 160 | call f%open('test_write.csv',n_cols=4,status_ok=status_ok) 161 | if (status_ok) then 162 | 163 | ! add header 164 | call f%add(['x','y','z','t']) 165 | call f%next_row() 166 | 167 | ! add some data: 168 | call f%add([1.0_wp,2.0_wp,3.0_wp],real_fmt='(F5.3)') 169 | call f%add(.true.) 170 | call f%next_row() 171 | call f%add([4.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 172 | call f%add(.false.) 173 | call f%next_row() 174 | call f%add([1.5_sp,2.5_sp,3.5_sp],real_fmt='(F5.3)') 175 | call f%add(.true.) 176 | call f%next_row() 177 | call f%add([4.5_sp,5.5_sp,6.5_sp],real_fmt='(F5.3)') 178 | call f%add(.false.) 179 | call f%next_row() 180 | 181 | ! finished 182 | call f%close(status_ok) 183 | 184 | else 185 | error stop 'could not open file' 186 | end if 187 | 188 | end subroutine csv_write_test 189 | 190 | subroutine csv_read_test() 191 | 192 | implicit none 193 | 194 | type(csv_file) :: f 195 | character(len=30),dimension(:),allocatable :: header 196 | real(wp),dimension(:),allocatable :: x,y,z 197 | real(sp),dimension(:),allocatable :: u,v,w 198 | logical,dimension(:),allocatable :: t 199 | logical :: status_ok 200 | integer,dimension(:),allocatable :: itypes 201 | 202 | write(*,*) '' 203 | write(*,*) '============================' 204 | write(*,*) ' csv_read_test ' 205 | write(*,*) '============================' 206 | write(*,*) '' 207 | 208 | ! read the file 209 | call f%read('test_write.csv',header_row=1,status_ok=status_ok) 210 | 211 | if (status_ok) then 212 | 213 | ! get the header and type info 214 | call f%get_header(header,status_ok) 215 | call f%variable_types(itypes,status_ok) 216 | 217 | ! get some data 218 | call f%get(1,x,status_ok) 219 | call f%get(2,y,status_ok) 220 | call f%get(3,z,status_ok) 221 | call f%get(4,t,status_ok) 222 | 223 | write(*,*) 'x=',x 224 | write(*,*) 'y=',y 225 | write(*,*) 'z=',z 226 | write(*,*) 't=',t 227 | 228 | call f%get(1,u,status_ok) 229 | call f%get(2,v,status_ok) 230 | call f%get(3,w,status_ok) 231 | call f%get(4,t,status_ok) 232 | 233 | write(*,*) 'x=',u 234 | write(*,*) 'y=',v 235 | write(*,*) 'z=',w 236 | write(*,*) 't=',t 237 | 238 | ! destroy the file 239 | call f%destroy() 240 | 241 | else 242 | error stop 'could not open file' 243 | end if 244 | 245 | end subroutine csv_read_test 246 | 247 | function file_exists(file) result(exists) 248 | 249 | !! returns True if the file exists 250 | 251 | implicit none 252 | character(len=*),intent(in) :: file 253 | logical :: exists 254 | 255 | integer :: istat 256 | 257 | inquire(file=file,exist=exists,iostat=istat) 258 | 259 | exists = exists .and. istat==0 ! just in case? 260 | 261 | end function file_exists 262 | 263 | end program csv_test 264 | !***************************************************************************************** 265 | -------------------------------------------------------------------------------- /test/csv_test2.f90: -------------------------------------------------------------------------------- 1 | 2 | 3 | !***************************************************************************************** 4 | !> author: Jacob Williams 5 | ! license: BSD 6 | ! 7 | ! Test of reading a CSV file. 8 | 9 | program csv_test2 10 | 11 | use csv_module 12 | use iso_fortran_env, only: wp => real64, sp => real32 13 | 14 | implicit none 15 | 16 | type(csv_file) :: file 17 | logical :: status_ok 18 | 19 | write(*,*) '' 20 | write(*,*) '============================' 21 | write(*,*) ' csv_test_2 ' 22 | write(*,*) '============================' 23 | write(*,*) '' 24 | 25 | call file%read('files/test2.csv',header_row=1,status_ok=status_ok) 26 | write(*,*) 'status_ok = ', status_ok 27 | 28 | end program csv_test2 -------------------------------------------------------------------------------- /test/csv_test3.f90: -------------------------------------------------------------------------------- 1 | !***************************************************************************************** 2 | !> 3 | ! Test of string conversions that uncover bugs in Gfortran. 4 | 5 | program csv_test3 6 | 7 | use csv_module 8 | use iso_fortran_env, only: wp => real64 9 | 10 | implicit none 11 | 12 | type(csv_file) :: f 13 | logical :: status_ok 14 | character(len=30),dimension(:),allocatable :: header, col1 15 | integer :: i 16 | character(len=100) :: tmp_str 17 | integer,dimension(:),allocatable :: int_col 18 | logical,dimension(:),allocatable :: log_col 19 | 20 | write(*,*) '' 21 | write(*,*) '============================' 22 | write(*,*) ' csv_test_3 ' 23 | write(*,*) '============================' 24 | write(*,*) '' 25 | 26 | ! set optional inputs: 27 | call f%initialize(verbose = .true.) 28 | 29 | ! open the file 30 | call f%open('test.csv',n_cols=6,status_ok=status_ok) 31 | 32 | ! add header 33 | call f%add(['x ','y ','z ','t ','int','str']) 34 | call f%next_row() 35 | 36 | ! add some data: 37 | call f%add([1.0_wp,2.0_wp,3.0_wp],real_fmt='(F5.3)') 38 | call f%add(.true.) 39 | call f%add(1) 40 | call f%add('a') 41 | call f%next_row() 42 | call f%add([2.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 43 | call f%add(.false.) 44 | call f%add(1) 45 | call f%add('b') 46 | call f%next_row() 47 | call f%add([3.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 48 | call f%add(.false.) 49 | call f%add(1) 50 | call f%add('c') 51 | call f%next_row() 52 | call f%add([4.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 53 | call f%add(.false.) 54 | call f%add(1) 55 | call f%add('d') 56 | call f%next_row() 57 | call f%add([5.0_wp,5.0_wp,6.0_wp],real_fmt='(F5.3)') 58 | call f%add(.false.) 59 | call f%add(1) 60 | call f%add('e') 61 | call f%next_row() 62 | 63 | ! finished 64 | call f%close(status_ok) 65 | if (.not. status_ok) error stop 'error closing file' 66 | 67 | ! read the file 68 | call f%read('test.csv',header_row=1,status_ok=status_ok) 69 | if (.not. status_ok) error stop 'error reading file' 70 | 71 | ! get the header and type info 72 | call f%get_header(header,status_ok) 73 | 74 | print "(*(g0))", "HEADER:" 75 | do i = 1, size(header) 76 | print "(*(g0))", ">"//trim(header(i))//"<" 77 | end do 78 | if (.not. all(header == ['x ','y ','z ','t ', 'int', 'str'])) error stop 'error reading header' 79 | 80 | call f%get(1,col1,status_ok) 81 | print "(*(g0))", "col1:" 82 | do i = 1, size(col1) 83 | print "(*(g0))", ">",trim(col1(i)),"<" 84 | end do 85 | do i = 1, 5 86 | write(tmp_str,'(F5.3)') real(i,wp) 87 | if (col1(i) /= tmp_str) error stop 'error converting cell to string:'//tmp_str 88 | end do 89 | 90 | call f%get(4,log_col,status_ok) 91 | if (.not. all(log_col .eqv. [.true.,.false.,.false.,.false.,.false.])) error stop 'error getting logical column' 92 | 93 | call f%get(5,int_col,status_ok) 94 | if (.not. all(int_col==1)) error stop 'error getting integer column' 95 | 96 | ! destroy the file 97 | call f%destroy() 98 | 99 | end program csv_test3 100 | -------------------------------------------------------------------------------- /test/csv_test4.f90: -------------------------------------------------------------------------------- 1 | !***************************************************************************************** 2 | !> 3 | ! Test of [[csv_utilities]]. 4 | 5 | program csv_test3 6 | 7 | use csv_utilities 8 | use iso_fortran_env, only: wp => real64 9 | 10 | implicit none 11 | 12 | integer,dimension(:),allocatable :: ivec 13 | 14 | write(*,*) '' 15 | write(*,*) '============================' 16 | write(*,*) ' csv_test_4 ' 17 | write(*,*) '============================' 18 | write(*,*) '' 19 | 20 | allocate(ivec(10000)) 21 | 22 | ivec = 0 23 | ivec(500) = 1 24 | 25 | call sort_ascending(ivec) 26 | if (.not. all(ivec(1:10000-1)==0)) error stop 'sort error' 27 | if (ivec(10000)/=1) error stop 'sort error' 28 | 29 | ivec = unique(ivec,chunk_size=10) 30 | if (.not. all (ivec == [0,1])) error stop 'unique error' 31 | 32 | write(*,*) 'PASSED' 33 | 34 | end program csv_test3 35 | --------------------------------------------------------------------------------