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 | ||