Initial fgof-process scaffold
- SHA
02c19e3e7ba4d36cb320c92799347a2682446cf2- Tree
21c0fd5
02c19e3
02c19e3e7ba4d36cb320c92799347a2682446cf221c0fd5| Status | File | + | - |
|---|---|---|---|
| A |
.gitignore
|
2 | 0 |
| A |
LICENSE
|
21 | 0 |
| A |
README.md
|
62 | 0 |
| A |
docs/ROADMAP.md
|
23 | 0 |
| A |
fpm.toml
|
20 | 0 |
| A |
src/fgof_process.f90
|
88 | 0 |
| A |
test/test_command.f90
|
15 | 0 |
.gitignoreadded@@ -0,0 +1,2 @@ | ||
| 1 | +build/ | |
| 2 | +.DS_Store | |
LICENSEadded@@ -0,0 +1,21 @@ | ||
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copyright (c) 2026 FortranGoingOnForty | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. | |
README.mdadded@@ -0,0 +1,62 @@ | ||
| 1 | +# fgof-process | |
| 2 | + | |
| 3 | +POSIX-first process and subprocess helpers for modern Fortran applications. | |
| 4 | + | |
| 5 | +`fgof-process` is intended to be a small, standalone library that gives Fortran tools a more ergonomic process API than raw `execute_command_line` or ad hoc C interop. | |
| 6 | + | |
| 7 | +Initial scope: | |
| 8 | + | |
| 9 | +- argv-first command construction | |
| 10 | +- synchronous process execution | |
| 11 | +- environment overrides | |
| 12 | +- working-directory overrides | |
| 13 | +- stdout and stderr capture | |
| 14 | +- exit-status reporting | |
| 15 | +- timeout-aware execution | |
| 16 | + | |
| 17 | +Future scope: | |
| 18 | + | |
| 19 | +- streaming process handles | |
| 20 | +- async spawn and wait | |
| 21 | +- signal helpers | |
| 22 | +- PTY-friendly integration points for a future `fgof-pty` | |
| 23 | + | |
| 24 | +## Status | |
| 25 | + | |
| 26 | +Early scaffold. | |
| 27 | + | |
| 28 | +This repository is being created as the first package in the FortranGoingOnForty reusable library family and is intended to be consumed standalone or via the umbrella catalog repo at `lib-modules`. | |
| 29 | + | |
| 30 | +## Package Goals | |
| 31 | + | |
| 32 | +- keep the API small and predictable | |
| 33 | +- prefer argv-based execution over shell-string execution | |
| 34 | +- make tests easy to write | |
| 35 | +- stay useful for shells, editors, TUI apps, and developer tooling | |
| 36 | + | |
| 37 | +## Planned API Shape | |
| 38 | + | |
| 39 | +Primary module: | |
| 40 | + | |
| 41 | +- `fgof_process` | |
| 42 | + | |
| 43 | +Initial public types: | |
| 44 | + | |
| 45 | +- `process_command` | |
| 46 | +- `process_result` | |
| 47 | +- `process_options` | |
| 48 | + | |
| 49 | +Initial public procedures: | |
| 50 | + | |
| 51 | +- `command` | |
| 52 | +- `run` | |
| 53 | + | |
| 54 | +## Development Notes | |
| 55 | + | |
| 56 | +- POSIX first: macOS and Linux | |
| 57 | +- standalone `fpm` package | |
| 58 | +- intended to remain independently versioned and releasable | |
| 59 | + | |
| 60 | +## License | |
| 61 | + | |
| 62 | +TBD | |
docs/ROADMAP.mdadded@@ -0,0 +1,23 @@ | ||
| 1 | +# Roadmap | |
| 2 | + | |
| 3 | +## v0.1 | |
| 4 | + | |
| 5 | +- establish standalone repo and `fpm` package | |
| 6 | +- define initial public types | |
| 7 | +- implement argv-first command construction | |
| 8 | +- implement synchronous `run` | |
| 9 | +- add basic tests | |
| 10 | + | |
| 11 | +## v0.2 | |
| 12 | + | |
| 13 | +- stdout and stderr capture | |
| 14 | +- cwd and env overrides | |
| 15 | +- timeout handling | |
| 16 | +- richer errors | |
| 17 | + | |
| 18 | +## v0.3 | |
| 19 | + | |
| 20 | +- async spawn or wait | |
| 21 | +- process handles | |
| 22 | +- kill and terminate helpers | |
| 23 | +- integration-test utilities | |
fpm.tomladded@@ -0,0 +1,20 @@ | ||
| 1 | +name = "fgof-process" | |
| 2 | +version = "0.1.0" | |
| 3 | +license = "MIT" | |
| 4 | +author = "FortranGoingOnForty" | |
| 5 | +maintainer = "FortranGoingOnForty" | |
| 6 | +copyright = "2026" | |
| 7 | +description = "POSIX-first process and subprocess helpers for modern Fortran applications" | |
| 8 | + | |
| 9 | +[build] | |
| 10 | +auto-executables = false | |
| 11 | +auto-tests = true | |
| 12 | +auto-examples = false | |
| 13 | + | |
| 14 | +[install] | |
| 15 | +library = true | |
| 16 | + | |
| 17 | +[fortran] | |
| 18 | +implicit-typing = false | |
| 19 | +implicit-external = false | |
| 20 | +source-form = "free" | |
src/fgof_process.f90added@@ -0,0 +1,88 @@ | ||
| 1 | +module fgof_process | |
| 2 | + implicit none | |
| 3 | + private | |
| 4 | + | |
| 5 | + public :: process_command | |
| 6 | + public :: process_options | |
| 7 | + public :: process_result | |
| 8 | + public :: command | |
| 9 | + public :: run | |
| 10 | + | |
| 11 | + type :: process_command | |
| 12 | + character(len=:), allocatable :: program | |
| 13 | + character(len=:), allocatable :: argv(:) | |
| 14 | + end type process_command | |
| 15 | + | |
| 16 | + type :: process_options | |
| 17 | + character(len=:), allocatable :: cwd | |
| 18 | + logical :: capture_stdout = .false. | |
| 19 | + logical :: capture_stderr = .false. | |
| 20 | + integer :: timeout_ms = 0 | |
| 21 | + character(len=:), allocatable :: env(:) | |
| 22 | + end type process_options | |
| 23 | + | |
| 24 | + type :: process_result | |
| 25 | + integer :: exit_code = -1 | |
| 26 | + logical :: timed_out = .false. | |
| 27 | + character(len=:), allocatable :: stdout | |
| 28 | + character(len=:), allocatable :: stderr | |
| 29 | + character(len=:), allocatable :: error_message | |
| 30 | + end type process_result | |
| 31 | + | |
| 32 | +contains | |
| 33 | + | |
| 34 | + function command(program, argv) result(cmd) | |
| 35 | + character(len=*), intent(in) :: program | |
| 36 | + character(len=*), intent(in), optional :: argv(:) | |
| 37 | + type(process_command) :: cmd | |
| 38 | + integer :: i | |
| 39 | + | |
| 40 | + cmd%program = trim(program) | |
| 41 | + | |
| 42 | + if (present(argv)) then | |
| 43 | + allocate(character(len=len_trim(program)) :: cmd%argv(0)) | |
| 44 | + deallocate(cmd%argv) | |
| 45 | + allocate(character(len=max(1, max_trimmed_length(argv))) :: cmd%argv(size(argv))) | |
| 46 | + do i = 1, size(argv) | |
| 47 | + cmd%argv(i) = trim(argv(i)) | |
| 48 | + end do | |
| 49 | + else | |
| 50 | + allocate(character(len=1) :: cmd%argv(0)) | |
| 51 | + end if | |
| 52 | + end function command | |
| 53 | + | |
| 54 | + function run(cmd, options) result(res) | |
| 55 | + type(process_command), intent(in) :: cmd | |
| 56 | + type(process_options), intent(in), optional :: options | |
| 57 | + type(process_result) :: res | |
| 58 | + | |
| 59 | + res%exit_code = -1 | |
| 60 | + res%timed_out = .false. | |
| 61 | + res%stdout = "" | |
| 62 | + res%stderr = "" | |
| 63 | + res%error_message = "run() is not implemented yet" | |
| 64 | + | |
| 65 | + if (.not. allocated(cmd%program)) then | |
| 66 | + res%error_message = "command program is not set" | |
| 67 | + return | |
| 68 | + end if | |
| 69 | + | |
| 70 | + if (present(options)) then | |
| 71 | + if (options%timeout_ms < 0) then | |
| 72 | + res%error_message = "timeout_ms must be >= 0" | |
| 73 | + return | |
| 74 | + end if | |
| 75 | + end if | |
| 76 | + end function run | |
| 77 | + | |
| 78 | + integer function max_trimmed_length(values) result(max_len) | |
| 79 | + character(len=*), intent(in) :: values(:) | |
| 80 | + integer :: i | |
| 81 | + | |
| 82 | + max_len = 1 | |
| 83 | + do i = 1, size(values) | |
| 84 | + max_len = max(max_len, len_trim(values(i))) | |
| 85 | + end do | |
| 86 | + end function max_trimmed_length | |
| 87 | + | |
| 88 | +end module fgof_process | |
test/test_command.f90added@@ -0,0 +1,15 @@ | ||
| 1 | +program test_command | |
| 2 | + use fgof_process, only : process_command, command | |
| 3 | + implicit none | |
| 4 | + | |
| 5 | + type(process_command) :: cmd | |
| 6 | + | |
| 7 | + cmd = command("printf", ["hello", "world"]) | |
| 8 | + | |
| 9 | + if (.not. allocated(cmd%program)) error stop "program not allocated" | |
| 10 | + if (cmd%program /= "printf") error stop "program mismatch" | |
| 11 | + if (.not. allocated(cmd%argv)) error stop "argv not allocated" | |
| 12 | + if (size(cmd%argv) /= 2) error stop "argv size mismatch" | |
| 13 | + if (cmd%argv(1) /= "hello") error stop "argv(1) mismatch" | |
| 14 | + if (cmd%argv(2) /= "world") error stop "argv(2) mismatch" | |
| 15 | +end program test_command | |