markdown · 6236 bytes Raw Blame History

fgof-process

CI

POSIX-first process and subprocess helpers for modern Fortran applications.

fgof-process is intended to be a small, standalone library that gives Fortran tools a more ergonomic process API than raw execute_command_line, thin POSIX wrappers, or experimental process surfaces that still feel too low-level for real tooling.

It is built for the kind of Fortran programs that need to orchestrate other tools: shells, editors, TUI apps, developer tooling, test fixtures, and automation helpers.

It is the first package in the FortranGoingOnForty lib-modules catalog, but it is intended to stand on its own as a normal fpm package.

Current v1 target:

  • argv-first command construction
  • synchronous process execution on macOS and Linux
  • explicit shell-command convenience through /bin/sh -c
  • environment overrides
  • working-directory overrides
  • stdin support
  • stdout and stderr capture
  • exit-status reporting
  • timeout-aware execution
  • structured, result-first errors

Future scope:

  • streaming process handles
  • async spawn and wait
  • signal helpers
  • PTY-friendly integration points for a future fgof-pty
  • a dedicated fgof-proc-test companion package

Status

Sync-first v0.1.1 is released.

The core synchronous path is implemented and tested. v0.1.1 carries the post-v0.1.0 C interface binding wrap needed by downstream CI compilers.

Implemented today:

  • public fgof_process and fgof_process_types modules
  • command() and shell() constructors
  • synchronous argv and shell execution
  • child-only cwd override
  • child-only env set and unset behavior
  • stdin piping
  • stdout capture
  • stderr capture
  • timeout enforcement with partial output preservation
  • result-first error codes for invalid commands, spawn failures, exec failures, and pipe setup failures

Still deferred:

  • async process handles
  • streaming process handles

Why Use It

  • argv-first execution is the default, so spaces and shell metacharacters stay literal unless you explicitly choose shell()
  • cwd and environment overrides apply only to the child process
  • stdout, stderr, stdin, exit status, and timeout handling live in one small API
  • nonzero child exits are reported as process outcomes, not library failures
  • result values are structured enough for test code and tool code to branch on cleanly

Package Goals

  • keep the API small and predictable
  • prefer argv-based execution over shell-string execution
  • make tests easy to write
  • stay useful for shells, editors, TUI apps, and developer tooling
  • close a real ecosystem gap rather than mirroring stdlib_system

Public API Shape

Primary module:

  • fgof_process

Public types:

  • process_command
  • process_options
  • process_result

Public procedures:

  • command
  • shell
  • run

Quick Start

program demo_run
  use fgof_process, only : FGOF_PROCESS_OK, command, process_options, process_result, run
  implicit none

  character(len=6) :: argv(2)
  type(process_options) :: opts
  type(process_result) :: res

  argv = [character(len=6) :: "%s", "hello"]
  opts%capture_stdout = .true.

  res = run(command("printf", argv), opts)
  if (res%error_code /= FGOF_PROCESS_OK) error stop trim(res%error_message)
  if (res%exit_code /= 0) error stop "child command failed"

  print "(A)", res%stdout
end program demo_run

Build And Test

fpm test

That is the baseline verification command locally and in CI.

command() vs shell()

Choose command() when you already know the executable and argument list.

res = run(command("git", [character(len=7) :: "status", "--short"]), opts)

This path does not interpret pipes, redirects, semicolons, or $VARS. They are passed through as literal argv values.

Choose shell() only when you need shell features such as pipelines or redirection.

res = run(shell("printf 'hello' | sed 's/hello/world/'"), opts)

This path uses /bin/sh -c on POSIX systems.

Common Patterns

Capture stdout:

opts = process_options()
opts%capture_stdout = .true.
res = run(command("pwd"), opts)

Capture stderr:

opts = process_options()
opts%capture_stderr = .true.
res = run(shell("printf 'oops' >&2"), opts)

Pipe stdin into a child:

opts = process_options()
opts%stdin = "hello" // new_line("a")
res = run(shell("read value; test ""$value"" = ""hello"""), opts)

Run in a different working directory:

opts = process_options()
opts%cwd = "/tmp"
res = run(command("pwd"), opts)

Set or unset child-only environment variables:

opts = process_options()
opts%env_set = ["FGOF_MODE=test"]
opts%env_unset = ["OLD_ENV_FLAG"]
res = run(shell("env"), opts)

Enforce a timeout and keep partial output:

opts = process_options()
opts%capture_stdout = .true.
opts%timeout_ms = 100
res = run(shell("printf 'begin'; sleep 2"), opts)

Result Semantics

run() returns a process_result with three kinds of information:

  • launch and completion state: launched, completed, timed_out, exited_normally
  • child outcome: exit_code, term_signal, captured stdout, captured stderr
  • library outcome: error_code, error_message, elapsed_ms

Important rule:

  • a nonzero child exit_code is not a library error
  • invalid input, pipe setup failure, spawn failure, exec failure, and timeout are library errors surfaced through error_code

Supported Platforms

  • macOS
  • Linux
  • GitHub Actions CI validates the package on macos-latest and ubuntu-latest with the GCC toolchain and fpm v0.13.0

Current Boundaries

  • v1 uses a direct POSIX backend on macOS and Linux.
  • stdlib_system can inform naming and behavior, but is not a required dependency.
  • Async process handles are explicitly deferred until after the sync-first release.

Development Notes

  • POSIX first: macOS and Linux
  • standalone fpm package
  • intended to remain independently versioned and releasable

License

MIT

View source
1 # fgof-process
2
3 [![CI](https://github.com/FortranGoingOnForty/fgof-process/actions/workflows/ci.yml/badge.svg)](https://github.com/FortranGoingOnForty/fgof-process/actions/workflows/ci.yml)
4
5 POSIX-first process and subprocess helpers for modern Fortran applications.
6
7 `fgof-process` is intended to be a small, standalone library that gives Fortran tools a more ergonomic process API than raw `execute_command_line`, thin POSIX wrappers, or experimental process surfaces that still feel too low-level for real tooling.
8
9 It is built for the kind of Fortran programs that need to orchestrate other tools: shells, editors, TUI apps, developer tooling, test fixtures, and automation helpers.
10
11 It is the first package in the [FortranGoingOnForty lib-modules](https://github.com/FortranGoingOnForty/lib-modules) catalog, but it is intended to stand on its own as a normal `fpm` package.
12
13 Current v1 target:
14
15 - argv-first command construction
16 - synchronous process execution on macOS and Linux
17 - explicit shell-command convenience through `/bin/sh -c`
18 - environment overrides
19 - working-directory overrides
20 - stdin support
21 - stdout and stderr capture
22 - exit-status reporting
23 - timeout-aware execution
24 - structured, result-first errors
25
26 Future scope:
27
28 - streaming process handles
29 - async spawn and wait
30 - signal helpers
31 - PTY-friendly integration points for a future `fgof-pty`
32 - a dedicated `fgof-proc-test` companion package
33
34 ## Status
35
36 Sync-first `v0.1.1` is released.
37
38 The core synchronous path is implemented and tested. `v0.1.1` carries the
39 post-`v0.1.0` C interface binding wrap needed by downstream CI compilers.
40
41 Implemented today:
42
43 - public `fgof_process` and `fgof_process_types` modules
44 - `command()` and `shell()` constructors
45 - synchronous argv and shell execution
46 - child-only cwd override
47 - child-only env set and unset behavior
48 - stdin piping
49 - stdout capture
50 - stderr capture
51 - timeout enforcement with partial output preservation
52 - result-first error codes for invalid commands, spawn failures, exec failures, and pipe setup failures
53
54 Still deferred:
55
56 - async process handles
57 - streaming process handles
58
59 ## Why Use It
60
61 - argv-first execution is the default, so spaces and shell metacharacters stay literal unless you explicitly choose `shell()`
62 - cwd and environment overrides apply only to the child process
63 - stdout, stderr, stdin, exit status, and timeout handling live in one small API
64 - nonzero child exits are reported as process outcomes, not library failures
65 - result values are structured enough for test code and tool code to branch on cleanly
66
67 ## Package Goals
68
69 - keep the API small and predictable
70 - prefer argv-based execution over shell-string execution
71 - make tests easy to write
72 - stay useful for shells, editors, TUI apps, and developer tooling
73 - close a real ecosystem gap rather than mirroring `stdlib_system`
74
75 ## Public API Shape
76
77 Primary module:
78
79 - `fgof_process`
80
81 Public types:
82
83 - `process_command`
84 - `process_options`
85 - `process_result`
86
87 Public procedures:
88
89 - `command`
90 - `shell`
91 - `run`
92
93 ## Quick Start
94
95 ```fortran
96 program demo_run
97 use fgof_process, only : FGOF_PROCESS_OK, command, process_options, process_result, run
98 implicit none
99
100 character(len=6) :: argv(2)
101 type(process_options) :: opts
102 type(process_result) :: res
103
104 argv = [character(len=6) :: "%s", "hello"]
105 opts%capture_stdout = .true.
106
107 res = run(command("printf", argv), opts)
108 if (res%error_code /= FGOF_PROCESS_OK) error stop trim(res%error_message)
109 if (res%exit_code /= 0) error stop "child command failed"
110
111 print "(A)", res%stdout
112 end program demo_run
113 ```
114
115 ## Build And Test
116
117 ```bash
118 fpm test
119 ```
120
121 That is the baseline verification command locally and in CI.
122
123 ## `command()` vs `shell()`
124
125 Choose `command()` when you already know the executable and argument list.
126
127 ```fortran
128 res = run(command("git", [character(len=7) :: "status", "--short"]), opts)
129 ```
130
131 This path does not interpret pipes, redirects, semicolons, or `$VARS`. They are passed through as literal argv values.
132
133 Choose `shell()` only when you need shell features such as pipelines or redirection.
134
135 ```fortran
136 res = run(shell("printf 'hello' | sed 's/hello/world/'"), opts)
137 ```
138
139 This path uses `/bin/sh -c` on POSIX systems.
140
141 ## Common Patterns
142
143 Capture stdout:
144
145 ```fortran
146 opts = process_options()
147 opts%capture_stdout = .true.
148 res = run(command("pwd"), opts)
149 ```
150
151 Capture stderr:
152
153 ```fortran
154 opts = process_options()
155 opts%capture_stderr = .true.
156 res = run(shell("printf 'oops' >&2"), opts)
157 ```
158
159 Pipe stdin into a child:
160
161 ```fortran
162 opts = process_options()
163 opts%stdin = "hello" // new_line("a")
164 res = run(shell("read value; test ""$value"" = ""hello"""), opts)
165 ```
166
167 Run in a different working directory:
168
169 ```fortran
170 opts = process_options()
171 opts%cwd = "/tmp"
172 res = run(command("pwd"), opts)
173 ```
174
175 Set or unset child-only environment variables:
176
177 ```fortran
178 opts = process_options()
179 opts%env_set = ["FGOF_MODE=test"]
180 opts%env_unset = ["OLD_ENV_FLAG"]
181 res = run(shell("env"), opts)
182 ```
183
184 Enforce a timeout and keep partial output:
185
186 ```fortran
187 opts = process_options()
188 opts%capture_stdout = .true.
189 opts%timeout_ms = 100
190 res = run(shell("printf 'begin'; sleep 2"), opts)
191 ```
192
193 ## Result Semantics
194
195 `run()` returns a `process_result` with three kinds of information:
196
197 - launch and completion state: `launched`, `completed`, `timed_out`, `exited_normally`
198 - child outcome: `exit_code`, `term_signal`, captured `stdout`, captured `stderr`
199 - library outcome: `error_code`, `error_message`, `elapsed_ms`
200
201 Important rule:
202
203 - a nonzero child `exit_code` is not a library error
204 - invalid input, pipe setup failure, spawn failure, exec failure, and timeout are library errors surfaced through `error_code`
205
206 ## Supported Platforms
207
208 - macOS
209 - Linux
210 - GitHub Actions CI validates the package on `macos-latest` and `ubuntu-latest` with the GCC toolchain and `fpm v0.13.0`
211
212 ## Current Boundaries
213
214 - v1 uses a direct POSIX backend on macOS and Linux.
215 - `stdlib_system` can inform naming and behavior, but is not a required dependency.
216 - Async process handles are explicitly deferred until after the sync-first release.
217
218 ## Development Notes
219
220 - POSIX first: macOS and Linux
221 - standalone `fpm` package
222 - intended to remain independently versioned and releasable
223
224 ## License
225
226 MIT