Skip to content

feat: creating and removing empty directories #1011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions doc/specs/stdlib_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,117 @@ The function returns a `logical` value:

---

## `make_directory` - Creates an empty directory

### Status

Experimental

### Description

It creates an empty directory with default permissions.
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 [,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`(optional): Shall be of type `state_type`, and is used for error handling. It is an `optional, intent(out)` argument.

### Return values

`err` is an optional state return flag. If not requested and an error occurs, an `FS_ERROR` will trigger an error stop.

### Example

```fortran
{!example/system/example_make_directory.f90!}
```

---

## `make_directory_all` - Creates an empty directory with all its parent directories

### Status

Experimental

### Description

It creates an empty directory with default permissions.
It also creates all the necessary parent directories in the path if they do not exist already.

### Syntax

`call [[stdlib_system(module):make_directory_all(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`(optional): Shall be of type `state_type`, and is used for error handling. It is an `optional, intent(out)` argument.

### Return values

`err` is an optional state return flag. If not requested and an error occurs, an `FS_ERROR` will trigger an error stop.

### 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`(optional): Shall be of type `state_type`, and is used for error handling. It is an `optional, intent(out)` argument.

### Return values

`err` is an optional state return flag. On error if not requested, an `FS_ERROR` will trigger an error stop.

### Example

```fortran
{!example/system/example_remove_directory.f90!}
```

---

## `null_device` - Return the null device file path

### Status
Expand Down
3 changes: 2 additions & 1 deletion example/system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ ADD_EXAMPLE(path_join)
ADD_EXAMPLE(path_split_path)
ADD_EXAMPLE(path_base_name)
ADD_EXAMPLE(path_dir_name)

ADD_EXAMPLE(make_directory)
ADD_EXAMPLE(remove_directory)
25 changes: 25 additions & 0 deletions example/system/example_make_directory.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
! Illustrate the usage of `make_directory`, `make_directory_all`
program example_make_directory
use stdlib_system, only: make_directory, make_directory_all
use stdlib_error, only: state_type
implicit none

type(state_type) :: err

call make_directory("temp_dir", err)

if (err%error()) then
print *, err%print()
else
print *, "directory created sucessfully"
end if

call make_directory_all("d1/d2/d3/d4", err)

if (err%error()) then
print *, err%print()
else
print *, "nested directories created sucessfully"
end if

end program example_make_directory
17 changes: 17 additions & 0 deletions example/system/example_remove_directory.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
! Illustrate the usage of `remove_directory`
program example_remove_directory
use stdlib_system, only: 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
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ set(fppFiles
stdlib_specialfunctions_gamma.fypp
stdlib_specialfunctions.fypp
stdlib_specialmatrices.fypp
stdlib_specialmatrices_tridiagonal.fypp
stdlib_specialmatrices_tridiagonal.fypp
stdlib_stats.fypp
stdlib_stats_corr.fypp
stdlib_stats_cov.fypp
Expand Down Expand Up @@ -118,6 +118,7 @@ set(SRC
stdlib_system_subprocess.c
stdlib_system_subprocess.F90
stdlib_system_path.f90
stdlib_system.c
stdlib_system.F90
stdlib_sparse.f90
stdlib_specialfunctions_legendre.f90
Expand Down
177 changes: 176 additions & 1 deletion src/stdlib_system.F90
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ 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, to_string
use stdlib_strings, only: to_c_char, find
use stdlib_string_type, only: string_type
use stdlib_optval, only: optval
use stdlib_error, only: state_type, STDLIB_SUCCESS, STDLIB_FS_ERROR
implicit none
private
Expand Down Expand Up @@ -109,6 +110,52 @@ 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 default permissions.
!!
!! ### Description
!! This function makes an empty directory according to the path provided.
!! Relative paths are supported. On Windows, paths involving either `/` or `\` are accepted.
!! An appropriate error message is returned whenever any error occurs.
!!
public :: make_directory

!! version: experimental
!!
!! Makes an empty directory, also creating all the parent directories required.
!! ([Specification](../page/specs/stdlib_system.html#make_directory))
!!
!! ### Summary
!! Creates an empty directory with all the parent directories required to do so.
!!
!! ### Description
!! This function makes an empty directory according to the path provided.
!! It also creates all the necessary parent directories in the path if they do not exist already.
!! Relative paths are supported.
!! An appropriate error message is returned whenever any error occurs.
!!
public :: make_directory_all

!! version: experimental
!!
!! Removes an empty directory.
!! ([Specification](../page/specs/stdlib_system.html#remove_directory))
!!
!! ### Summary
!! Removes an empty directory.
!!
!! ### Description
!! This function Removes an empty directory according to the path provided.
!! Relative paths are supported. On Windows paths involving either `/` or `\` are accepted.
!! An appropriate error message is returned whenever any error occurs.
!!
public :: remove_directory

!! version: experimental
!!
!! Deletes a specified file from the filesystem.
Expand Down Expand Up @@ -849,6 +896,134 @@ end function stdlib_is_directory

end function is_directory

! A helper function to get the result of the C function `strerror`.
! `strerror` is a function provided by `<string.h>`.
! It returns a string describing the meaning of `errno` in the C header `<errno.h>`
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, err)
character(len=*), intent(in) :: path
type(state_type), optional, intent(out) :: err

integer :: code
type(state_type) :: err0

interface
integer function stdlib_make_directory(cpath) bind(C, name='stdlib_make_directory')
import c_char
character(kind=c_char), intent(in) :: cpath(*)
end function stdlib_make_directory
end interface

code = stdlib_make_directory(to_c_char(trim(path)))

if (code /= 0) then
err0 = FS_ERROR_CODE(code, c_get_strerror())
call err0%handle(err)
end if

end subroutine make_directory

subroutine make_directory_all(path, err)
character(len=*), intent(in) :: path
type(state_type), optional, intent(out) :: err

integer :: i, indx
type(state_type) :: err0
character(len=1) :: sep
logical :: is_dir, check_is_dir

sep = path_sep()
i = 1
indx = find(path, sep, i)
check_is_dir = .true.

do
! Base case to exit the loop
if (indx == 0) then
is_dir = is_directory(path)

if (.not. is_dir) then
call make_directory(path, err0)

if (err0%error()) then
call err0%handle(err)
end if
end if

return
end if

if (check_is_dir) then
is_dir = is_directory(path(1:indx))
end if

if (.not. is_dir) then
! no need for further `is_dir` checks
! all paths going forward need to be created
check_is_dir = .false.
call make_directory(path(1:indx), err0)

if (err0%error()) then
call err0%handle(err)
return
end if
end if

i = i + 1 ! the next occurence of `sep`
indx = find(path, sep, i)
end do
end subroutine make_directory_all

!! 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)))

if (code /= 0) then
err0 = FS_ERROR_CODE(code, c_get_strerror())
call err0%handle(err)
end if

end subroutine remove_directory

!> Returns the file path of the null device for the current operating system.
!>
!> Version: Helper function.
Expand Down
Loading
Loading