Skip to content

stdlib_system: essential path functionality #999

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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 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
194 changes: 183 additions & 11 deletions doc/specs/stdlib_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ The result is a real value representing the elapsed time in seconds, measured fr

### Syntax

`delta_t = ` [[stdlib_system(module):elapsed(subroutine)]] `(process)`
`delta_t = ` [[stdlib_system(module):elapsed(interface)]] `(process)`

### Arguments

Expand Down Expand Up @@ -212,7 +212,7 @@ in case of process hang or delay.

### Syntax

`call ` [[stdlib_system(module):wait(subroutine)]] `(process [, max_wait_time])`
`call ` [[stdlib_system(module):wait(interface)]] `(process [, max_wait_time])`

### Arguments

Expand Down Expand Up @@ -243,7 +243,7 @@ This is especially useful for monitoring asynchronous processes and retrieving t

### Syntax

`call ` [[stdlib_system(module):update(subroutine)]] `(process)`
`call ` [[stdlib_system(module):update(interface)]] `(process)`

### Arguments

Expand All @@ -269,7 +269,7 @@ This interface is useful when a process needs to be forcefully stopped, for exam

### Syntax

`call ` [[stdlib_system(module):kill(subroutine)]] `(process, success)`
`call ` [[stdlib_system(module):kill(interface)]] `(process, success)`

### Arguments

Expand Down Expand Up @@ -298,7 +298,7 @@ It ensures that the requested sleep duration is honored on both Windows and Unix

### Syntax

`call ` [[stdlib_system(module):sleep(subroutine)]] `(millisec)`
`call ` [[stdlib_system(module):sleep(interface)]] `(millisec)`

### Arguments

Expand All @@ -324,7 +324,7 @@ This function is highly efficient and works during the compilation phase, avoidi

### Syntax

`result = ` [[stdlib_system(module):is_windows(function)]] `()`
`result = ` [[stdlib_system(module):is_windows(interface)]] `()`

### Return Value

Expand Down Expand Up @@ -359,7 +359,7 @@ If the OS cannot be identified, the function returns `OS_UNKNOWN`.

### Syntax

`os = [[stdlib_system(module):get_runtime_os(function)]]()`
`os = ` [[stdlib_system(module):get_runtime_os(function)]] `()`

### Class

Expand Down Expand Up @@ -396,7 +396,7 @@ This caching mechanism ensures negligible overhead for repeated calls, unlike `g

### Syntax

`os = [[stdlib_system(module):OS_TYPE(function)]]()`
`os = ` [[stdlib_system(module):OS_TYPE(function)]]`()`

### Class

Expand Down Expand Up @@ -431,7 +431,7 @@ It is designed to work across multiple platforms. On Windows, paths with both fo

### Syntax

`result = [[stdlib_system(module):is_directory(function)]] (path)`
`result = ` [[stdlib_system(module):is_directory(function)]]`(path)`

### Class

Expand Down Expand Up @@ -471,7 +471,7 @@ It reads as an empty file. The null device's path varies by operating system:

### Syntax

`path = [[stdlib_system(module):null_device(function)]]()`
`path = ` [[stdlib_system(module):null_device(function)]]`()`

### Class

Expand Down Expand Up @@ -506,7 +506,7 @@ The function provides an optional error-handling mechanism via the `state_type`

### Syntax

`call [[stdlib_system(module):delete_file(subroutine)]] (path [, err])`
`call ` [[stdlib_system(module):delete_file(subroutine)]]` (path [, err])`

### Class
Subroutine
Expand All @@ -532,3 +532,175 @@ The file is removed from the filesystem if the operation is successful. If the o
```fortran
{!example/system/example_delete_file.f90!}
```

## `join_path` - Joins the provided paths according to the OS

### Status

Experimental

### Description

This interface joins the paths provided to it according to the platform specific path-separator.
i.e `\` for windows and `/` for others

### Syntax

`res = ` [[stdlib_system(module):join_path(interface)]] ` (p1, p2)`

`res = ` [[stdlib_system(module):join_path(interface)]] ` (p)`

### Class
Pure function

### Arguments

`p1, p2`: Shall be a character string. It is an `intent(in)` argument.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because stdlib offers a string type, it would be nice if these string manipulation functions were interfaces working with either character strings (as currently proposed), or the internal string_type type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made these functions interfaces keeping this possibility in mind... But what is considered a good way of doing this? Writing procedures for string_type and then, when calling them with character variables just assign these to the new string_type variables and call the string_type version of the interface itself or code duplication, keeping the code separate for these two although they are in essence the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I got it!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: the easiest option is to keep the character implementation and then return the result to a string via move:

!> Moves the allocated character scalar from 'from' to 'to'
!> [Specifications](../page/specs/stdlib_string_type.html#move)
interface move
module procedure :: move_string_string
module procedure :: move_string_char
module procedure :: move_char_string
module procedure :: move_char_char
end interface move

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the relevant changes... But it doesn't use move, it uses assignment(=) and char, Let me know what you think of it.

Copy link
Member

@perazz perazz Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works @wassup05, however, there are two copies: string->char for the input arguments, and then char -> string on the assignment. Using move would at least avoid incurring the last copy. Just do:

character(:), allocatable :: join_char
join_char = join_path(char(a),char(b))
call move(from=join_char,to=...result variable...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the internal string_type representation is just an allocatable character variable, I would also support (to be discussed with the community as a separate PR) the implementation of a tiny zero-copy "view" function for the string type, see here:

    !> Zero-copy view of the string as a character array
    pure function view(string) result(char_array)
        type(string_type), intent(in), target :: string
        character(:), pointer :: char_array
        if (allocated(string%raw)) then
            char_array => string%raw
        else
            nullify(char_array)
        end if
    end function view

or
`p`: Shall be a list of character strings. `intent(in)` argument.

### Return values

The resultant path.

## `operator(/)`

Alternative syntax to`join_path` using an overloaded operator. Join two paths according to the platform specific path-separator.

### Status

Experimental

### Syntax

`p = lval / rval`

### Class

Pure function.

### Arguments

`lval`: A character string, `intent(in)`.

`rval`: A character string, `intent(in)`.

### Result value

The result is an `allocatable` character string

#### Example

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

## `split_path` - splits a path immediately following the last separator

### Status

Experimental

### Description

This subroutine splits a path immediately following the last separator after removing the trailing separators
splitting it into most of the times a directory and a file name.

### Syntax

`call `[[stdlib_system(module):split_path(interface)]]`(p, head, tail)`

### Class
Subroutine

### Arguments

`p`: A character string containing the path to be split. `intent(in)`
`head`: The first part of the path. `allocatable, intent(out)`
`tail`: The rest part of the path. `allocatable, intent(out)`

### Behavior

- If `p` is empty, `head` is set to `.` and `tail` is empty
- If `p` consists entirely of path-separators. `head` is set to the path-separator and `tail` is empty
- `head` ends in a path-separator if and only if `p` appears to be a root directory or child of one

### Return values

The splitted path. `head` and `tail`.

### Example

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

## `base_name` - The last part of a path

### Status

Experimental

### Description

This function returns the last part of a path after removing trailing path separators.

### Syntax

`res = ` [[stdlib_system(module):base_name(interface)]]`(p)`

### Class
Function

### Arguments

`p`: the path, a character string, `intent(in)`

### Behavior

- The `tail` of `stdlib_system(module):split_path(interface)` is exactly what is returned. Same Behavior.

### Return values

A character string.

### Example

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

## `dir_name` - Everything except the last part of the path

### Status

Experimental

### Description

This function returns everything except the last part of a path.

### Syntax

`res = ` [[stdlib_system(module):dir_name(interface)]]`(p)`

### Class
Function

### Arguments

`p`: the path, a character string, `intent(in)`

### Behavior

- The `head` of `stdlib_system(module):split_path(interface)` is exactly what is returned. Same Behavior.

### Return values

A character string.

### Example

```fortran
{!example/system/example_path_dir_name.f90!}
```
4 changes: 4 additions & 0 deletions example/system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ ADD_EXAMPLE(process_5)
ADD_EXAMPLE(process_6)
ADD_EXAMPLE(process_7)
ADD_EXAMPLE(sleep)
ADD_EXAMPLE(path_join)
ADD_EXAMPLE(path_split_path)
ADD_EXAMPLE(path_base_name)
ADD_EXAMPLE(path_dir_name)
16 changes: 16 additions & 0 deletions example/system/example_path_base_name.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
! Usage of base_name
program example_path_base_name
use stdlib_system, only: base_name, OS_TYPE, OS_WINDOWS
character(len=:), allocatable :: p1

if(OS_TYPE() == OS_WINDOWS) then
p1 = 'C:\Users'
else
p1 = '/home'
endif

print *, 'base name of '// p1 // ' -> ' // base_name(p1)
! base name of C:\Users -> Users
! OR
! base name of /home -> home
end program example_path_base_name
16 changes: 16 additions & 0 deletions example/system/example_path_dir_name.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
! Usage of dir_name
program example_path_dir_name
use stdlib_system, only: dir_name, OS_TYPE, OS_WINDOWS
character(len=:), allocatable :: p1, head, tail

if(OS_TYPE() == OS_WINDOWS) then
p1 = 'C:\Users' ! C:\Users
else
p1 = '/home' ! /home
endif

print *, 'dir_name of '// p1 // ' -> ' // dir_name(p1)
! dir_name of C:\Users -> C:\
! OR
! dir_name of /home -> /
end program example_path_dir_name
23 changes: 23 additions & 0 deletions example/system/example_path_join.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
! Usage of join_path, operator(/)
program example_path_join
use stdlib_system, only: join_path, operator(/), OS_TYPE, OS_WINDOWS
character(len=:), allocatable :: p1, p2, p3
character(len=20) :: parr(4)

if(OS_TYPE() == OS_WINDOWS) then
p1 = 'C:'/'Users'/'User1'/'Desktop'
p2 = join_path('C:\Users\User1', 'Desktop')
parr = [character(len=20) :: 'C:', 'Users', 'User1', 'Desktop']
p3 = join_path(parr)
else
p1 = ''/'home'/'User1'/'Desktop'
p2 = join_path('/home/User1', 'Desktop')
parr = [character(len=20) :: '', 'home', 'User1', 'Desktop']
p3 = join_path(parr)
end if

! (p1 == p2 == p3) = '/home/User1/Desktop' OR 'C:\Users\User1\Desktop'
print *, p1 ! /home/User1/Desktop OR 'C:\Users\User1\Desktop'
print *, "p1 == p2: ", p1 == p2 ! T
print *, "p2 == p3: ", p2 == p3 ! T
end program example_path_join
25 changes: 25 additions & 0 deletions example/system/example_path_split_path.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
! Usage of split_path
program example_path_split_path
use stdlib_system, only: join_path, split_path, OS_TYPE, OS_WINDOWS
character(len=:), allocatable :: p1, head, tail

if(OS_TYPE() == OS_WINDOWS) then
p1 = join_path('C:\Users\User1', 'Desktop') ! C:\Users\User1\Desktop
else
p1 = join_path('/home/User1', 'Desktop') ! /home/User1/Desktop
endif

call split_path(p1, head, tail)
! head = /home/User1 OR C:\Users\User1, tail = Desktop
print *, p1 // " -> " // head // " + " // tail
! C:\Users\User1\Desktop -> C:\Users\User1 + Desktop
! OR
! /home/User1/Desktop -> /home/User1 + Desktop

call split_path(head, p1, tail)
! p1 = /home OR C:\Users, tail = User1
print *, head // " -> " // p1 // " + " // tail
! C:\Users\User1 -> C:\Users + User1
! OR
! /home/User1 -> /home + User1
end program example_path_split_path
6 changes: 3 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -91,7 +91,6 @@ set(fppFiles
# Preprocessed files to contain preprocessor directives -> .F90
set(cppFiles
stdlib_linalg_constants.fypp

stdlib_linalg_blas.fypp
stdlib_linalg_lapack.fypp
)
Expand All @@ -116,6 +115,7 @@ set(SRC
stdlib_sorting_radix_sort.f90
stdlib_system_subprocess.c
stdlib_system_subprocess.F90
stdlib_system_path.f90
stdlib_system.F90
stdlib_sparse.f90
stdlib_specialfunctions_legendre.f90
Expand Down
Loading
Loading