Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU AND CMAKE_Fortran_COMPILER_VERSION VER
message(FATAL_ERROR "GCC Version 9 or newer required")
endif()

# Convert CMAKE_SYSTEM_NAME to uppercase
string(TOUPPER "${CMAKE_SYSTEM_NAME}" SYSTEM_NAME_UPPER)

# Pass the uppercase system name as a macro
add_compile_options(-D${SYSTEM_NAME_UPPER})

# --- compiler feature checks
include(CheckFortranSourceCompiles)
include(CheckFortranSourceRuns)
Expand Down
2 changes: 2 additions & 0 deletions config/fypp_deployment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import platform
import fypp
import argparse
from joblib import Parallel, delayed
Expand Down Expand Up @@ -115,6 +116,7 @@ def fpm_build(args,unknown):
for idx, arg in enumerate(unknown):
if arg.startswith("--flag"):
flags= flags + unknown[idx+1]
flags = flags + "-D{}".format(platform.system().upper())
#==========================================
# build with fpm
subprocess.run("fpm build"+
Expand Down
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!}
```

## `joinpath` - 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):joinpath(interface)]] ` (p1, p2)`

`res = ` [[stdlib_system(module):joinpath(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`joinpath` 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!}
```

## `splitpath` - 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):splitpath(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_splitpath.f90!}
```

## `basename` - 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):basename(interface)]]`(p)`

### Class
Function

### Arguments

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

### Behavior

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

### Return values

A character string.

### Example

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

## `dirname` - 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):dirname(interface)]]`(p)`

### Class
Function

### Arguments

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

### Behavior

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

### Return values

A character string.

### Example

```fortran
{!example/system/example_path_dirname.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_basename)
ADD_EXAMPLE(path_dirname)
ADD_EXAMPLE(path_join)
ADD_EXAMPLE(path_splitpath)
16 changes: 16 additions & 0 deletions example/system/example_path_basename.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
! Usage of splitpath, dirname, basename
program example_path_splitpath
use stdlib_system, only: basename, ISWIN
character(len=:), allocatable :: p1

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

print *, 'basename of '// p1 // ' -> ' // basename(p1)
! basename of C:\Users -> Users
! OR
! basename of /home -> home
end program example_path_splitpath
16 changes: 16 additions & 0 deletions example/system/example_path_dirname.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
! Usage of splitpath, dirname, basename
program example_path_splitpath
use stdlib_system, only: dirname, ISWIN
character(len=:), allocatable :: p1, head, tail

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

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

if( ISWIN ) then
p1 = 'C:'/'Users'/'User1'/'Desktop'
p2 = joinpath('C:\Users\User1', 'Desktop')
else
p1 = ''/'home'/'User1'/'Desktop'
p2 = joinpath('/home/User1', 'Desktop')
end if

parr = [character(len=20) :: '', 'home', 'User1', 'Desktop']
p3 = joinpath(parr)

! (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_splitpath.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
! Usage of splitpath, dirname, basename
program example_path_splitpath
use stdlib_system, only: joinpath, splitpath, ISWIN
character(len=:), allocatable :: p1, head, tail

if( ISWIN ) then
p1 = joinpath('C:\Users\User1', 'Desktop') ! C:\Users\User1\Desktop
else
p1 = joinpath('/home/User1', 'Desktop') ! /home/User1/Desktop
endif

call splitpath(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 splitpath(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_splitpath
Loading
Loading