Skip to content
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

Add filesystem interaction #874

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
144 changes: 143 additions & 1 deletion doc/specs/stdlib_io.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,149 @@ Provides a text file called `filename` that contains the rank-2 `array`.
{!example/io/example_savetxt.f90!}
```

## `is_windows`

### Status

Experimental

### Description

Returns a logical value indicating whether the current operating system is Windows.

### Syntax

`is_windows = ` [[stdlib_io_filesystem(module):is_windows(function)]] `()`

### Return value

A logical value indicating whether the current operating system is Windows.

## `path_separator`

### Status

Experimental

### Description

Returns the path separator for the current operating system.

### Syntax

`separator = ` [[stdlib_io_filesystem(module):path_separator(function)]] `()`

### Return value

A character value containing the path separator for the current operating system.

## `exists`

### Status

Experimental

### Description

Determines if a file or directory exists at the given path by returning a logical value.

### Syntax

`exists = ` [[stdlib_io_filesystem(module):exists(function)]] `(path)`

### Arguments

`path`: Shall be a character expression containing the path to a file or directory to check for existence.

### Return value

A logical value indicating whether a file or directory exists at the given path.

## `list_dir`

### Status

Experimental

### Description

Lists the contents of a directory.

### Syntax

`call ` [[stdlib_io_filesystem(module):list_dir(subroutine)]] `(dir, files[, iostat][, iomsg])`

### Arguments

`dir`: Shall be a character expression containing the path to the directory to list.

`files`: Shall be an allocatable rank-1 array of type `string_type` that will contain the names of the files and directories in the directory.

`iostat`: Shall be a scalar of type `integer` that receives the error status of `list_dir`. Optional argument.

`iomsg`: Shall be a deferred length character variable that receives the error message of `list_dir`. Optional argument.

## `mkdir`

### Status

Experimental

### Description

Creates a new directory.

### Syntax

`call ` [[stdlib_io_filesystem(module):mkdir(subroutine)]] `(dir[, iostat][, iomsg])`

### Arguments

`dir`: Shall be a character expression containing the path to the directory to create.

`iostat`: Shall be a scalar of type `integer` that receives the error status of `mkdir`. Optional argument.

`iomsg`: Shall be a deferred length character variable that receives the error message of `mkdir`. Optional argument.

## `rmdir`

### Status

Experimental

### Description

Removes a directory.

### Syntax

`call ` [[stdlib_io_filesystem(module):rmdir(subroutine)]] `(dir)`

### Arguments

`dir`: Shall be a character expression containing the path to the directory to remove.

## `run`

### Status

Experimental

### Description

Runs a command in the shell.

### Syntax

`call ` [[stdlib_io_filesystem(module):run(subroutine)]] `(command[, iostat][, iomsg])`

### Arguments

`command`: Shall be a character expression containing the command to run in the shell.

`iostat`: Shall be a scalar of type `integer` that receives the error status of `run`. Optional argument.

`iomsg`: Shall be a deferred length character variable that receives the error message of `run`. Optional argument.

## `load_npy`

Expand Down Expand Up @@ -164,7 +307,6 @@ Returns an allocated `array` with the content of `filename` in case of success.
{!example/io/example_loadnpy.f90!}
```


## `save_npy`

### Status
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ set(SRC
stdlib_hashmaps.f90
stdlib_hashmap_chaining.f90
stdlib_hashmap_open.f90
stdlib_io_filesystem.F90
stdlib_logger.f90
stdlib_sorting_radix_sort.f90
stdlib_system.F90
Expand Down
181 changes: 181 additions & 0 deletions src/stdlib_io_filesystem.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
! SPDX-Identifier: MIT

!> Interaction with the filesystem.
module stdlib_io_filesystem
use stdlib_string_type, only: string_type
implicit none
private

public :: temp_dir, is_windows, exists, path_separator, list_dir, mkdir, rmdir, run

character(*), parameter :: temp_dir = 'temp'

contains

!> Version: experimental
!>
!> Whether the operating system is Windows.
!> [Specification](../page/specs/stdlib_io.html#is_windows)
logical function is_windows()
minhqdao marked this conversation as resolved.
Show resolved Hide resolved
character(len=255) :: value
integer :: length, stat

call get_environment_variable('OSTYPE', value, length, stat)
if (stat == 0 .and. length > 0 .and. (index(value, 'win') > 0 .or. index(value, 'msys') > 0)) then
is_windows = .true.; return
end if

call get_environment_variable('OS', value, length, stat)
if (stat == 0 .and. length > 0 .and. index(value, 'Windows_NT') > 0) then
is_windows = .true.; return
end if

is_windows = .false.
end

!> Version: experimental
!>
!> Returns the path separator for the current operating system.
!> [Specification](../page/specs/stdlib_io.html#path_separator)
character function path_separator()
if (is_windows()) then
path_separator = '\'
else
path_separator = '/'
end if
end

!> Version: experimental
!>
!> Whether a file or directory exists at the given path.
!> [Specification](../page/specs/stdlib_io.html#exists)
logical function exists(path)
!> Path to a file or directory.
character(len=*), intent(in) :: path

inquire(file=path, exist=exists)

#if defined(__INTEL_COMPILER)
if (.not. exists) inquire(directory=path, exist=exists)
#endif
end

!> Version: experimental
!>
!> List files and directories of a directory. Does not list hidden files.
!> [Specification](../page/specs/stdlib_io.html#list_dir)
subroutine list_dir(dir, files, iostat, iomsg)
!> Directory to list.
character(len=*), intent(in) :: dir
!> List of files and directories.
type(string_type), allocatable, intent(out) :: files(:)
!> Status of listing.
integer, optional, intent(out) :: iostat
!> Error message.
character(len=:), allocatable, optional, intent(out) :: iomsg

integer :: unit, stat
character(len=256) :: line
character(:), allocatable :: listed_contents

stat = 0

if (.not. exists(temp_dir)) then
call mkdir(temp_dir, stat)
if (stat /= 0) then
if (present(iostat)) iostat = stat
if (present(iomsg)) iomsg = "Failed to create temporary directory '"//temp_dir//"'."
return
end if
end if

listed_contents = temp_dir//path_separator()//'listed_contents.txt'

if (is_windows()) then
call run('dir /b '//dir//' > '//listed_contents, stat)
else
call run('ls '//dir//' > '//listed_contents, stat)
end if
if (stat /= 0) then
if (present(iostat)) iostat = stat
if (present(iomsg)) iomsg = "Failed to list files in directory '"//dir//"'."
return
end if

open(newunit=unit, file=listed_contents, status='old', action='read', iostat=stat)
if (stat /= 0) then
if (present(iostat)) iostat = stat
if (present(iomsg)) iomsg = "Failed to open file '"//listed_contents//"'."
return
end if

allocate(files(0))
do
read(unit, '(A)', iostat=stat) line
if (stat /= 0) exit
files = [files, string_type(line)]
end do
close(unit, status="delete")
end

!> Version: experimental
!>
!> Create a directory.
!> [Specification](../page/specs/stdlib_io.html#mkdir)
subroutine mkdir(dir, iostat, iomsg)
character(len=*), intent(in) :: dir
integer, optional, intent(out) :: iostat
character(len=:), allocatable, optional, intent(out) :: iomsg

if (is_windows()) then
call run('mkdir '//dir, iostat, iomsg)
else
call run('mkdir -p '//dir, iostat, iomsg)
end if
end

!> Version: experimental
!>
!> Remove a directory including its contents.
!> [Specification](../page/specs/stdlib_io.html#rmdir)
subroutine rmdir(dir)
character(len=*), intent(in) :: dir

if (is_windows()) then
call run('rmdir /s/q '//dir)
else
call run('rm -rf '//dir)
end if
end

!> Version: experimental
!>
!> Run a command in the shell.
!> [Specification](../page/specs/stdlib_io.html#run)
subroutine run(command, iostat, iomsg)
!> Command to run.
character(len=*), intent(in) :: command
!> Status of the operation.
integer, intent(out), optional :: iostat
!> Error message.
character(len=:), allocatable, intent(out), optional :: iomsg

integer :: exitstat, cmdstat
character(len=256) :: cmdmsg

if (present(iostat)) iostat = 0
exitstat = 0; cmdstat = 0

call execute_command_line(command, exitstat=exitstat, cmdstat=cmdstat, cmdmsg=cmdmsg)
if (exitstat /= 0 .or. cmdstat /= 0) then
if (present(iostat)) then
if (exitstat /= 0) then
iostat = exitstat
else
iostat = cmdstat
end if
end if
if (present(iomsg) .and. trim(adjustl(cmdmsg)) /= '') iomsg = cmdmsg
end if
end
end
1 change: 1 addition & 0 deletions test/io/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ADDTEST(savetxt_qp)
set_tests_properties(loadtxt_qp PROPERTIES LABELS quadruple_precision)
set_tests_properties(savetxt_qp PROPERTIES LABELS quadruple_precision)

ADDTEST(filesystem)
ADDTEST(getline)
ADDTEST(npy)
ADDTEST(open)
Expand Down
Loading
Loading