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

io: getfile #939

Merged
merged 26 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
49 changes: 45 additions & 4 deletions doc/specs/stdlib_io.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Provides a npy file called `filename` that contains the rank-2 `array`.
{!example/io/example_savenpy.f90!}
```

## `getline`
## `get_line`

### Status

Expand All @@ -217,9 +217,9 @@ Read a whole line from a formatted unit into a string variable

### Syntax

`call ` [[stdlib_io(module):getline(interface)]] ` (unit, line[, iostat][, iomsg])`
`call ` [[stdlib_io(module):get_line(interface)]] ` (unit, line[, iostat][, iomsg])`

`call ` [[stdlib_io(module):getline(interface)]] ` (line[, iostat][, iomsg])`
`call ` [[stdlib_io(module):get_line(interface)]] ` (line[, iostat][, iomsg])`

### Arguments

Expand All @@ -241,7 +241,7 @@ Read a whole line from a formatted unit into a string variable
### Example

```fortran
{!example/io/example_getline.f90!}
{!example/io/example_get_line.f90!}
```

## Formatting constants
Expand All @@ -260,3 +260,44 @@ Provides formats for all kinds as defined in the `stdlib_kinds` module.
```fortran
{!example/io/example_fmt_constants.f90!}
```

## `get_file` - Read a whole ASCII file into a `character` or a `string` variable

### Status

Experimental

### Description

This subroutine interface reads the entirety of a specified ASCII file and returns its content as a string or an allocatable `character` variable.
The function provides an optional error-handling mechanism via the `state_type` class. If the `err` argument is not provided, exceptions will trigger an `error stop`. The function also supports an optional flag to delete the file after reading.

### Syntax

`call [[stdlib_io(module):get_file(subroutine)]] (filename, file [, err] [, delete=.false.])`

### Class
Function

### Arguments

`filename`: Shall be a character input containing the path to the ASCII file to read. It is an `intent(in)` argument.

`file`: Shall be a `type(string_type)` or an allocatable `character` variable containing the full content of the specified file. It is an `intent(out)` argument.

`err` (optional): Shall be a `type(state_type)` variable. It is an `intent(out)` argument used for error handling.

`delete` (optional): Shall be a `logical` flag. If `.true.`, the file is deleted after reading. Default is `.false.`. It is an `intent(in)` argument.

### Return values

Output variable `file` will contain the full content of the specified file.

Raises `STDLIB_IO_ERROR` if the file is not found, cannot be opened, read, or deleted.
Exceptions trigger an `error stop` unless the optional `err` argument is provided.

### Example

```fortran
{!example/io/example_get_file.f90!}
```
3 changes: 2 additions & 1 deletion example/io/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ADD_EXAMPLE(fmt_constants)
#ADD_EXAMPLE(getline)
#ADD_EXAMPLE(get_line)
ADD_EXAMPLE(get_file)
ADD_EXAMPLE(loadnpy)
ADD_EXAMPLE(loadtxt)
ADD_EXAMPLE(open)
Expand Down
20 changes: 20 additions & 0 deletions example/io/example_get_file.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
! Demonstrate usage of `get_file`
program example_get_file
use stdlib_io, only: get_file
use stdlib_string_type, only: string_type
use stdlib_error, only: state_type
implicit none

character(*), parameter :: filename = "example.txt"
type(string_type) :: filecontent
type(state_type) :: err

! Read a file into a string
call get_file(filename, filecontent, err=err)

if (err%error()) then
print *, err%print()
else
print *, "Success! File "//filename//" imported."
end if
end program example_get_file
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
program example_getline
use, intrinsic :: iso_fortran_env, only: input_unit, output_unit
use stdlib_io, only: getline
use stdlib_io, only: get_line
implicit none
character(len=:), allocatable :: line
integer :: stat

call getline(input_unit, line, stat)
call get_line(input_unit, line, stat)
do while (stat == 0)
write (output_unit, '(a)') line
call getline(input_unit, line, stat)
call get_line(input_unit, line, stat)
end do
end program example_getline
179 changes: 158 additions & 21 deletions src/stdlib_io.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,36 @@ module stdlib_io
use, intrinsic :: iso_fortran_env, only : input_unit
use stdlib_kinds, only: sp, dp, xdp, qp, &
int8, int16, int32, int64
use stdlib_error, only: error_stop
use stdlib_error, only: error_stop, state_type, STDLIB_IO_ERROR
use stdlib_optval, only: optval
use stdlib_ascii, only: is_blank
use stdlib_string_type, only : string_type
use stdlib_string_type, only : string_type, assignment(=), move
implicit none
private
! Public API
public :: loadtxt, savetxt, open, getline
public :: loadtxt, savetxt, open, get_line, get_file

!! version: experimental
!!
!! Reads a whole ASCII file and loads its contents into a string variable.
!! ([Specification](../page/specs/stdlib_io.html#get-file-read-a-whole-ascii-file-into-a-character-or-a-string-variable))
!!
!!### Summary
!! Subroutine interface for reading the content of a file into a string.
!!
!!### Description
!!
!! This subroutine reads the entirety of a specified ASCII file and returns it as a string. The optional
!! `err` argument allows for handling errors through the library's `state_type` class.
!! An optional `logical` flag can be passed to delete the file after reading.
!!
!!@note Handles errors using the library's `state_type` error-handling class. If not provided,
!! exceptions will trigger an `error stop`.
!!
interface get_file
module procedure :: get_file_char
module procedure :: get_file_string
end interface get_file

! Private API that is exposed so that we can test it in tests
public :: parse_mode
Expand Down Expand Up @@ -51,12 +73,12 @@ module stdlib_io
!> Version: experimental
!>
!> Read a whole line from a formatted unit into a string variable
interface getline
module procedure :: getline_char
module procedure :: getline_string
module procedure :: getline_input_char
module procedure :: getline_input_string
end interface getline
interface get_line
module procedure :: get_line_char
module procedure :: get_line_string
module procedure :: get_line_input_char
module procedure :: get_line_input_string
end interface get_line

interface loadtxt
!! version: experimental
Expand Down Expand Up @@ -265,7 +287,7 @@ contains
number_of_columns = 0

! Read first non-skipped line as a whole
call getline(s, line, ios)
call get_line(s, line, ios)
if (ios/=0 .or. .not.allocated(line)) return

lastblank = .true.
Expand Down Expand Up @@ -437,7 +459,7 @@ contains
!> Version: experimental
!>
!> Read a whole line from a formatted unit into a deferred length character variable
subroutine getline_char(unit, line, iostat, iomsg)
subroutine get_line_char(unit, line, iostat, iomsg)
!> Formatted IO unit
integer, intent(in) :: unit
!> Line to read
Expand Down Expand Up @@ -479,12 +501,12 @@ contains
else if (stat /= 0) then
call error_stop(trim(msg))
end if
end subroutine getline_char
end subroutine get_line_char

!> Version: experimental
!>
!> Read a whole line from a formatted unit into a string variable
subroutine getline_string(unit, line, iostat, iomsg)
subroutine get_line_string(unit, line, iostat, iomsg)
!> Formatted IO unit
integer, intent(in) :: unit
!> Line to read
Expand All @@ -496,36 +518,151 @@ contains

character(len=:), allocatable :: buffer

call getline(unit, buffer, iostat, iomsg)
call get_line(unit, buffer, iostat, iomsg)
line = string_type(buffer)
end subroutine getline_string
end subroutine get_line_string

!> Version: experimental
!>
!> Read a whole line from the standard input into a deferred length character variable
subroutine getline_input_char(line, iostat, iomsg)
subroutine get_line_input_char(line, iostat, iomsg)
!> Line to read
character(len=:), allocatable, intent(out) :: line
!> Status of operation
integer, intent(out), optional :: iostat
!> Error message
character(len=:), allocatable, optional :: iomsg

call getline(input_unit, line, iostat, iomsg)
end subroutine getline_input_char
call get_line(input_unit, line, iostat, iomsg)
end subroutine get_line_input_char

!> Version: experimental
!>
!> Read a whole line from the standard input into a string variable
subroutine getline_input_string(line, iostat, iomsg)
subroutine get_line_input_string(line, iostat, iomsg)
!> Line to read
type(string_type), intent(out) :: line
!> Status of operation
integer, intent(out), optional :: iostat
!> Error message
character(len=:), allocatable, optional :: iomsg

call getline(input_unit, line, iostat, iomsg)
end subroutine getline_input_string
call get_line(input_unit, line, iostat, iomsg)
end subroutine get_line_input_string

!> Version: experimental
!>
!> Reads a whole ASCII file and loads its contents into a string variable.
!> The function handles error states and optionally deletes the file after reading.
subroutine get_file_string(filename,file,err,delete)
!> Input file name
character(*), intent(in) :: filename
!> Output string variable
type(string_type), intent(out) :: file
!> [optional] State return flag. On error, if not requested, the code will stop.
type(state_type), optional, intent(out) :: err
!> [optional] Delete file after reading? Default: do not delete
logical, optional, intent(in) :: delete

! Local variables
character(len=:), allocatable :: filestring

! Process output
call get_file_char(filename,filestring,err,delete)
call move(from=fileString,to=file)

end subroutine get_file_string

!> Version: experimental
!>
!> Reads a whole ASCII file and loads its contents into an allocatable `character` variable.
!> The function handles error states and optionally deletes the file after reading.
subroutine get_file_char(filename,file,err,delete)
!> Input file name
character(*), intent(in) :: filename
!> Output string variable
character(len=:), allocatable, intent(out) :: file
!> [optional] State return flag. On error, if not requested, the code will stop.
type(state_type), optional, intent(out) :: err
!> [optional] Delete file after reading? Default: do not delete
logical, optional, intent(in) :: delete

! Local variables
type(state_type) :: err0
character(len=512) :: iomsg
integer :: lun,iostat
integer(int64) :: errpos,file_size
logical :: is_present,want_deleted

!> Check if the file should be deleted after reading
if (present(delete)) then
want_deleted = delete
else
want_deleted = .false.
end if

!> Check file existing
inquire(file=filename, exist=is_present)
if (.not.is_present) then
allocate(character(len=0) :: file)
err0 = state_type('get_file',STDLIB_IO_ERROR,'File not present:',filename)
call err0%handle(err)
return
end if

!> Retrieve file size
inquire(file=filename,size=file_size)

invalid_size: if (file_size<0) then

allocate(character(len=0) :: file)
err0 = state_type('get_file',STDLIB_IO_ERROR,filename,'has invalid size=',file_size)
call err0%handle(err)
return

endif invalid_size

! Read file
open(newunit=lun,file=filename, &
form='unformatted',action='read',access='stream',status='old', &
iostat=iostat,iomsg=iomsg)

if (iostat/=0) then
allocate(character(len=0) :: file)
err0 = state_type('get_file',STDLIB_IO_ERROR,'Cannot open',filename,'for read:',iomsg)
call err0%handle(err)
return
end if

allocate(character(len=file_size) :: file)

read_data: if (file_size>0) then

read(lun, pos=1, iostat=iostat, iomsg=iomsg) file

! Read error
if (iostat/=0) then

inquire(unit=lun,pos=errpos)
err0 = state_type('get_file',STDLIB_IO_ERROR,iomsg,'(',filename,'at byte',errpos,')')
call err0%handle(err)
return

endif

end if read_data

if (want_deleted) then
close(lun,iostat=iostat,status='delete')
if (iostat/=0) err0 = state_type('get_file',STDLIB_IO_ERROR,'Cannot delete',filename,'after reading')
else
close(lun,iostat=iostat)
if (iostat/=0) err0 = state_type('get_file',STDLIB_IO_ERROR,'Cannot close',filename,'after reading')
endif

! Process output
call err0%handle(err)

end subroutine get_file_char

end module stdlib_io
2 changes: 1 addition & 1 deletion test/io/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ADDTEST(savetxt_qp)
set_tests_properties(loadtxt_qp PROPERTIES LABELS quadruple_precision)
set_tests_properties(savetxt_qp PROPERTIES LABELS quadruple_precision)

ADDTEST(getline)
ADDTEST(get_line)
ADDTEST(npy)
ADDTEST(open)
ADDTEST(parse_mode)
Loading
Loading