diff --git a/doc/specs/stdlib_system.md b/doc/specs/stdlib_system.md index 96eebb2e8..bb5b1afa2 100644 --- a/doc/specs/stdlib_system.md +++ b/doc/specs/stdlib_system.md @@ -456,6 +456,82 @@ The function returns a `logical` value: --- +## `make_directory` - Creates an empty directory + +### Status + +Experimental + +### Description + +It creates an empty directory. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):make_directory(subroutine)]] (path, mode, err)` + +### Class + +Subroutine + +### Arguments + +`path`: Shall be a character string containing the path of the directory to create. It is an `intent(in)` argument. + +`mode`: Shall be a scalar integer indicating the permission bits required (Not applicable to Windows). It is an `optional, intent(in)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `optional, intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_make_directory.f90!} +``` + +--- + +## `remove_directory` - Removes an empty directory + +### Status + +Experimental + +### Description + +It deletes an empty directory. +It is designed to work across multiple platforms. On Windows, paths with both forward `/` and backward `\` slashes are accepted. + +### Syntax + +`call [[stdlib_system(module):remove_directory(subroutine)]] (path, err)` + +### Class + +Subroutine + +### Arguments + +`path`: Shall be a character string containing the path of the directory to create. It is an `intent(in)` argument. + +`err`: Shall be of type `state_type`, for error handling. It is an `intent(out)` argument. + +### Return values + +The `err` is set accordingly. + +### Example + +```fortran +{!example/system/example_remove_directory.f90!} +``` + +--- + ## `null_device` - Return the null device file path ### Status diff --git a/example/system/CMakeLists.txt b/example/system/CMakeLists.txt index a2a7525c9..8189d525b 100644 --- a/example/system/CMakeLists.txt +++ b/example/system/CMakeLists.txt @@ -11,3 +11,5 @@ ADD_EXAMPLE(process_5) ADD_EXAMPLE(process_6) ADD_EXAMPLE(process_7) ADD_EXAMPLE(sleep) +ADD_EXAMPLE(make_directory) +ADD_EXAMPLE(remove_directory) diff --git a/example/system/example_make_directory.f90 b/example/system/example_make_directory.f90 new file mode 100644 index 000000000..5e551b810 --- /dev/null +++ b/example/system/example_make_directory.f90 @@ -0,0 +1,17 @@ +! Illustrate the usage of make_directory +program example_make_directory + use stdlib_system, only: make_directory, is_directory + use stdlib_error, only: state_type + implicit none + + type(state_type) :: err + + call make_directory("test", err=err) + + if (err%error()) then + print *, err%print() + else + print *, "directory created sucessfully" + end if + +end program example_make_directory diff --git a/example/system/example_remove_directory.f90 b/example/system/example_remove_directory.f90 new file mode 100644 index 000000000..993adf4f9 --- /dev/null +++ b/example/system/example_remove_directory.f90 @@ -0,0 +1,17 @@ +! Illustrate the usage of remove_directory +program example_remove_directory + use stdlib_system, only: make_directory, is_directory, remove_directory + use stdlib_error, only: state_type + implicit none + + type(state_type) :: err + + call remove_directory("directory_to_be_removed", err) + + if (err%error()) then + print *, err%print() + else + print *, "directory removed successfully" + end if + +end program example_remove_directory diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3cd99120..24fd9c56b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,14 +32,14 @@ set(fppFiles stdlib_linalg_kronecker.fypp stdlib_linalg_cross_product.fypp stdlib_linalg_eigenvalues.fypp - stdlib_linalg_solve.fypp + stdlib_linalg_solve.fypp stdlib_linalg_determinant.fypp stdlib_linalg_qr.fypp stdlib_linalg_inverse.fypp stdlib_linalg_pinv.fypp stdlib_linalg_norms.fypp stdlib_linalg_state.fypp - stdlib_linalg_svd.fypp + stdlib_linalg_svd.fypp stdlib_linalg_cholesky.fypp stdlib_linalg_schur.fypp stdlib_optval.fypp @@ -116,6 +116,7 @@ set(SRC stdlib_sorting_radix_sort.f90 stdlib_system_subprocess.c stdlib_system_subprocess.F90 + stdlib_system.c stdlib_system.F90 stdlib_sparse.f90 stdlib_specialfunctions_legendre.f90 diff --git a/src/stdlib_system.F90 b/src/stdlib_system.F90 index a9c3e4d55..2e0d3a1aa 100644 --- a/src/stdlib_system.F90 +++ b/src/stdlib_system.F90 @@ -2,7 +2,7 @@ module stdlib_system use, intrinsic :: iso_c_binding, only : c_int, c_long, c_ptr, c_null_ptr, c_int64_t, c_size_t, & c_f_pointer use stdlib_kinds, only: int64, dp, c_bool, c_char -use stdlib_strings, only: to_c_char +use stdlib_strings, only: to_c_char, to_string use stdlib_error, only: state_type, STDLIB_SUCCESS, STDLIB_FS_ERROR implicit none private @@ -100,6 +100,36 @@ module stdlib_system !! public :: is_directory +!! version: experimental +!! +!! Makes an empty directory. +!! ([Specification](../page/specs/stdlib_system.html#make_directory)) +!! +!! ### Summary +!! Creates an empty directory with particular permissions. +!! +!! ### Description +!! This function makes an empty directory according to the path provided. +!! Relative paths as well as on Windows paths involving either `/` or `\` are accepted +!! appropriate error message is returned whenever any error occur. +!! +public :: make_directory + +!! version: experimental +!! +!! Removes an empty directory. +!! ([Specification](../page/specs/stdlib_system.html#remove_directory)) +!! +!! ### Summary +!! Deletes an empty directory. +!! +!! ### Description +!! This function deletes an empty directory according to the path provided. +!! Relative paths as well as on Windows paths involving either `/` or `\` are accepted. +!! appropriate error message is returned whenever any error occur. +!! +public :: remove_directory + !! version: experimental !! !! Deletes a specified file from the filesystem. @@ -690,6 +720,96 @@ end function stdlib_is_directory end function is_directory +function c_get_strerror() result(str) + character(len=:), allocatable :: str + + interface + type(c_ptr) function strerror(len) bind(C, name='stdlib_strerror') + import c_size_t, c_ptr + implicit none + integer(c_size_t), intent(out) :: len + end function strerror + end interface + + type(c_ptr) :: c_str_ptr + integer(c_size_t) :: len, i + character(kind=c_char), pointer :: c_str(:) + + c_str_ptr = strerror(len) + + call c_f_pointer(c_str_ptr, c_str, [len]) + + allocate(character(len=len) :: str) + + do concurrent (i=1:len) + str(i:i) = c_str(i) + end do +end function c_get_strerror + +!! makes an empty directory +subroutine make_directory(path, mode, err) + character(len=*), intent(in) :: path + integer, intent(in), optional :: mode + type(state_type), optional, intent(out) :: err + + integer :: code + type(state_type) :: err0 + + + interface + integer function stdlib_make_directory(cpath, cmode) bind(C, name='stdlib_make_directory') + import c_char + character(kind=c_char), intent(in) :: cpath(*) + integer, intent(in) :: cmode + end function stdlib_make_directory + end interface + + if (is_windows() .and. present(mode)) then + ! _mkdir() doesn't have a `mode` argument + err0 = state_type(STDLIB_FS_ERROR, "mode argument not present for Windows") + call err0%handle(err) + return + end if + + code = stdlib_make_directory(to_c_char(trim(path)), mode) + + select case (code) + case (0) + return + case default + ! error + err0 = state_type(STDLIB_FS_ERROR, "code:", to_string(code)//',', c_get_strerror()) + call err0%handle(err) + end select +end subroutine make_directory + +!! Removes an empty directory +subroutine remove_directory(path, err) + character(len=*), intent(in) :: path + type(state_type), optional, intent(out) :: err + + integer :: code + type(state_type) :: err0 + + interface + integer function stdlib_remove_directory(cpath) bind(C, name='stdlib_remove_directory') + import c_char + character(kind=c_char), intent(in) :: cpath(*) + end function stdlib_remove_directory + end interface + + code = stdlib_remove_directory(to_c_char(trim(path))) + + select case (code) + case (0) + return + case default + ! error + err0 = state_type(STDLIB_FS_ERROR, "code:", to_string(code)//',', c_get_strerror()) + call err0%handle(err) + end select +end subroutine remove_directory + !> Returns the file path of the null device for the current operating system. !> !> Version: Helper function. diff --git a/src/stdlib_system.c b/src/stdlib_system.c new file mode 100644 index 000000000..2d9368cc3 --- /dev/null +++ b/src/stdlib_system.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif /* ifdef _WIN32 */ + +char* stdlib_strerror(size_t* len){ + char* err = strerror(errno); + *len = strlen(err); + return err; +} + +int stdlib_make_directory(const char* path, mode_t mode){ + int code; +#ifdef _WIN32 + code = _mkdir(path); +#else + code = mkdir(path, mode); +#endif /* ifdef _WIN32 */ + + if (!code){ + return 0; + } + + return errno; +} + +int stdlib_remove_directory(const char* path){ + int code; +#ifdef _WIN32 + code = _rmdir(path); +#else + code = rmdir(path); +#endif /* ifdef _WIN32 */ + + if (!code){ + return 0; + } + + return errno; +} diff --git a/test/system/test_filesystem.f90 b/test/system/test_filesystem.f90 index 4cf1690e4..add6a9323 100644 --- a/test/system/test_filesystem.f90 +++ b/test/system/test_filesystem.f90 @@ -1,6 +1,6 @@ module test_filesystem use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test - use stdlib_system, only: is_directory, delete_file + use stdlib_system, only: is_directory, delete_file, make_directory, remove_directory use stdlib_error, only: state_type implicit none @@ -17,7 +17,11 @@ subroutine collect_suite(testsuite) new_unittest("fs_is_directory_file", test_is_directory_file), & new_unittest("fs_delete_non_existent", test_delete_file_non_existent), & new_unittest("fs_delete_existing_file", test_delete_file_existing), & - new_unittest("fs_delete_file_being_dir", test_delete_directory) & + new_unittest("fs_delete_file_being_dir", test_delete_directory), & + new_unittest("fs_make_dir", test_make_directory), & + new_unittest("fs_make_dir_existing_dir", test_make_directory_existing), & + new_unittest("fs_remove_dir", test_remove_directory), & + new_unittest("fs_remove_dir_non_existent", test_remove_directory_nonexistent) & ] end subroutine collect_suite @@ -145,7 +149,81 @@ subroutine test_delete_directory(error) if (allocated(error)) return end subroutine test_delete_directory + + subroutine test_make_directory(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call make_directory(filename, err=err) + call check(error, err%ok(), 'Could not make directory: '//err%print()) + if (allocated(error)) return + + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot cleanup make_directory test: '//trim(msg)) + end subroutine test_make_directory + subroutine test_make_directory_existing(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call execute_command_line('mkdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot init make_directory_existing test: '//trim(msg)) + if (allocated(error)) return + + call make_directory(filename, err=err) + call check(error, err%error(), 'Made an already existing directory somehow') + + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + if (allocated(error)) then + ! if previous error is allocated as well + call check(error, ios==0 .and. iocmd==0, error%message // ' and cannot cleanup make_directory test: '//trim(msg)) + return + end if + call check(error, ios==0 .and. iocmd==0, 'Cannot cleanup make_directory test: '//trim(msg)) + end subroutine test_make_directory_existing + + subroutine test_remove_directory(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + character(len=256) :: filename + integer :: ios,iocmd + character(len=512) :: msg + + filename = "test_directory" + + call execute_command_line('mkdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, 'Cannot init remove_directory test: '//trim(msg)) + if (allocated(error)) return + + call remove_directory(filename, err) + call check(error, err%ok(), 'Could not remove directory: '//err%print()) + if (allocated(error)) then + ! Clean up: remove the empty directory + call execute_command_line('rmdir ' // filename, exitstat=ios, cmdstat=iocmd, cmdmsg=msg) + call check(error, ios==0 .and. iocmd==0, error%message // ' and cannot cleanup make_directory test: '//trim(msg)) + end if + end subroutine test_remove_directory + + subroutine test_remove_directory_nonexistent(error) + type(error_type), allocatable, intent(out) :: error + type(state_type) :: err + + call remove_directory("random_name", err) + call check(error, err%error(), 'Somehow removed a non-existent directory!: ') + if (allocated(error)) return + end subroutine test_remove_directory_nonexistent end module test_filesystem