1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! `processkit` — child-process management for Rust.
//!
//! Two layers:
//!
//! - **[`ProcessGroup`]** — a kill-on-drop container for a process *tree*. Every
//! child spawned into the group, and everything those children spawn, dies
//! with the group, so an exiting or panicking owner never leaks subprocesses.
//! Containment is a Windows [Job Object], a Linux [cgroup v2] (with a POSIX
//! process-group fallback), a POSIX process group on macOS/BSD, or nothing on
//! other targets — observable via [`Mechanism`].
//! - **runner** — async run-and-capture built on the group. Describe a run with
//! [`Command`], then drive it to completion ([`Command::output_string`],
//! [`Command::run`], …) or start it via a [`ProcessRunner`] for streaming or a
//! shared group. The trait is the mock seam (see [`ScriptedRunner`]).
//!
//! Async throughout (tokio). Errors are the structured [`Error`]; a non-zero
//! exit is reported in [`ProcessResult`], not raised, until you call
//! [`ProcessResult::ensure_success`].
//!
//! **Run vocabulary** — the same verb means the same thing at every layer
//! ([`Command`], [`ProcessRunner`]/[`ProcessRunnerExt`], [`CliClient`]):
//!
//! - **`run` / `text`** — require a zero exit and return stdout as a `String`,
//! trailing whitespace trimmed (`trim_end`: the final newline is noise, but
//! leading whitespace can be significant).
//! - **`output` / `capture` / `output_string` / `output_bytes`** — return the
//! full [`ProcessResult`]; a non-zero exit is *not* an error here.
//! - **`exit_code` / `code`** — the exit code. On a [`ProcessResult`],
//! [`code`](ProcessResult::code) is `Option<i32>` (`None` for a run killed by
//! its timeout or a signal — there is no `-1` sentinel); the `exit_code`
//! helpers instead surface a missing code as an error.
//! - **`probe`** — run a predicate and read its exit code as a `bool`: `0` →
//! `true`, `1` → `false`, anything else is an error (`git diff --quiet`, …).
//!
//! ```no_run
//! # async fn demo() -> processkit::Result<()> {
//! use processkit::Command;
//!
//! // Capture output; a non-zero exit does not error on its own.
//! let result = Command::new("git").args(["rev-parse", "HEAD"]).output_string().await?;
//! println!("HEAD is {}", result.stdout().trim());
//!
//! // Or require success and get trimmed stdout directly.
//! let version = Command::new("cargo").arg("--version").run().await?;
//! # let _ = version;
//! # Ok(())
//! # }
//! ```
//!
//! # Recipes
//!
//! ```no_run
//! # use std::time::Duration;
//! # async fn demo() -> processkit::Result<()> {
//! use processkit::{Command, Error};
//!
//! // Exit code *is* the answer (0 = yes, 1 = no; anything else errors):
//! let clean = Command::new("git").args(["diff", "--quiet"]).probe().await?;
//!
//! // Retry a transient failure (replays the command; classifier inspects the error):
//! let fetched = Command::new("git")
//! .args(["fetch", "--quiet"])
//! .timeout(Duration::from_secs(10))
//! .retry(3, Duration::from_millis(200), |e| {
//! matches!(e, Error::Timeout { .. })
//! || e.diagnostic().is_some_and(|m| m.contains("Could not resolve host"))
//! })
//! .run()
//! .await;
//!
//! // A friendly failure message — stderr, falling back to stdout (git writes
//! // `CONFLICT …` / `nothing to commit` there):
//! if let Err(e) = Command::new("git").args(["merge", "topic"]).run().await {
//! eprintln!("merge failed: {}", e.diagnostic().unwrap_or("(no output)"));
//! }
//!
//! // Set an env var once for every command (typed CLI wrapper):
//! use processkit::CliClient;
//! let git = CliClient::new("git").default_env("GIT_TERMINAL_PROMPT", "0");
//! let _ = git.text(git.command(["status", "--porcelain"])).await?;
//! # let _ = (clean, fetched);
//! # Ok(())
//! # }
//! ```
//!
//! [Job Object]: https://learn.microsoft.com/windows/win32/procthread/job-objects
//! [cgroup v2]: https://docs.kernel.org/admin-guide/cgroup-v2.html
pub use ;
pub use CliClient;
pub use Command;
pub use ;
pub use Encoding;
pub use ;
pub use ;
pub use Mechanism;
pub use ProcessResult;
pub use ;
pub use ;
pub use ProcessGroupStats;
pub use ;
// Re-exported so callers can `use processkit::StreamExt;` to consume
// [`RunningProcess::stdout_lines`]'s [`StdoutLines`] stream (`.next().await`,
// combinators) without depending on `tokio-stream` directly.
pub use StreamExt;
// `cli_client!` is exported at the crate root via `#[macro_export]`.
use OsStr;
/// Run `program` with `args` inside a private job and return trimmed stdout, or
/// an [`Error`] on a non-zero exit / spawn failure / timeout. A thin shim over
/// [`Command`]; use the builder for a working directory, env, stdin, or timeout.
pub async
/// Run `program` with `args` inside a private job and capture the result
/// without erroring on a non-zero exit — for commands whose exit code is meaningful.
pub async
/// The `mockall`-generated mock of [`ProcessRunner`] (enabled by the `mock`
/// feature), re-exported under a friendlier name.
pub use MockProcessRunner as MockRunner;