Skip to main content

rskit_process/command/
io.rs

1//! Process I/O strategies and their capture / observation policies.
2
3#[cfg(unix)]
4use crate::pty::PtyIo;
5use crate::runner::OutputObserver;
6
7/// Default maximum retained bytes for each captured output stream.
8pub const DEFAULT_MAX_OUTPUT_BYTES: usize = 1024 * 1024;
9/// Standard input policy.
10#[derive(Debug, Clone, Eq, PartialEq, Default)]
11#[non_exhaustive]
12pub enum InputPolicy {
13    /// Close stdin for the child process.
14    #[default]
15    Closed,
16    /// Pipe predefined bytes to stdin, then close it.
17    Bytes(Vec<u8>),
18    /// Inherit stdin from the parent process.
19    Inherit,
20}
21
22/// Output capture policy for pipe-backed modes.
23#[derive(Debug, Clone, Eq, PartialEq)]
24pub struct OutputPolicy {
25    /// Whether stdout should be retained in `ProcessResult`.
26    pub capture_stdout: bool,
27    /// Whether stderr should be retained in `ProcessResult`.
28    pub capture_stderr: bool,
29    /// Maximum retained bytes for each captured stream. `None` means unbounded.
30    pub max_output_bytes: Option<usize>,
31}
32
33impl Default for OutputPolicy {
34    fn default() -> Self {
35        Self::captured()
36    }
37}
38
39impl OutputPolicy {
40    /// Capture stdout and stderr with the default bounds.
41    #[must_use]
42    pub const fn captured() -> Self {
43        Self {
44            capture_stdout: true,
45            capture_stderr: true,
46            max_output_bytes: Some(DEFAULT_MAX_OUTPUT_BYTES),
47        }
48    }
49
50    /// Do not retain stdout or stderr.
51    #[must_use]
52    pub const fn observe_only() -> Self {
53        Self {
54            capture_stdout: false,
55            capture_stderr: false,
56            max_output_bytes: Some(DEFAULT_MAX_OUTPUT_BYTES),
57        }
58    }
59
60    /// Set the maximum retained bytes for each captured output stream.
61    #[must_use]
62    pub fn with_max_output_bytes(mut self, bytes: usize) -> Self {
63        self.max_output_bytes = Some(bytes);
64        self
65    }
66
67    /// Disable output capture bounds.
68    #[must_use]
69    pub fn with_unbounded_output(mut self) -> Self {
70        self.max_output_bytes = None;
71        self
72    }
73}
74
75/// Pipe-backed deterministic capture mode.
76#[derive(Debug, Clone, Eq, PartialEq, Default)]
77pub struct CapturedIo {
78    /// Standard input policy.
79    pub input: InputPolicy,
80    /// Output capture policy.
81    pub output: OutputPolicy,
82}
83
84impl CapturedIo {
85    /// Create capture mode with default closed stdin and bounded stdout/stderr capture.
86    #[must_use]
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Set the stdin policy.
92    #[must_use]
93    pub fn with_input(mut self, input: InputPolicy) -> Self {
94        self.input = input;
95        self
96    }
97
98    /// Set the output policy.
99    #[must_use]
100    pub fn with_output(mut self, output: OutputPolicy) -> Self {
101        self.output = output;
102        self
103    }
104}
105
106/// Pipe-backed live observation mode with optional capture.
107#[derive(Debug, Clone, Default)]
108pub struct ObservedIo {
109    /// Standard input policy.
110    pub input: InputPolicy,
111    /// Output capture policy.
112    pub output: OutputPolicy,
113    /// Output callbacks.
114    pub observer: OutputObserver,
115}
116
117impl ObservedIo {
118    /// Create observed mode with the provided callbacks.
119    #[must_use]
120    pub fn new(observer: OutputObserver) -> Self {
121        Self {
122            observer,
123            ..Self::default()
124        }
125    }
126
127    /// Set the stdin policy.
128    #[must_use]
129    pub fn with_input(mut self, input: InputPolicy) -> Self {
130        self.input = input;
131        self
132    }
133
134    /// Set the output policy.
135    #[must_use]
136    pub fn with_output(mut self, output: OutputPolicy) -> Self {
137        self.output = output;
138        self
139    }
140}
141
142/// Inherited terminal stdio mode.
143#[derive(Debug, Clone, Eq, PartialEq)]
144pub struct InheritedIo {
145    /// Standard input policy. Defaults to inheriting parent stdin.
146    pub input: InputPolicy,
147}
148
149impl Default for InheritedIo {
150    fn default() -> Self {
151        Self {
152            input: InputPolicy::Inherit,
153        }
154    }
155}
156
157impl InheritedIo {
158    /// Create inherited stdio mode.
159    #[must_use]
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    /// Set the stdin policy while stdout/stderr remain inherited.
165    #[must_use]
166    pub fn with_input(mut self, input: InputPolicy) -> Self {
167        self.input = input;
168        self
169    }
170}
171
172/// Process I/O strategy.
173#[derive(Debug, Clone)]
174#[non_exhaustive]
175pub enum ProcessIo {
176    /// Capture stdout/stderr separately through pipes.
177    Captured(CapturedIo),
178    /// Observe stdout/stderr live through pipes with optional capture.
179    Observed(ObservedIo),
180    /// Inherit parent terminal stdio.
181    Inherited(InheritedIo),
182    /// Run the child on a pseudoterminal so it renders as in a real terminal.
183    ///
184    /// stdout and stderr are merged into one stream; see [`PtyIo`]. Unix-only.
185    #[cfg(unix)]
186    Pty(PtyIo),
187}
188
189impl Default for ProcessIo {
190    fn default() -> Self {
191        Self::Captured(CapturedIo::default())
192    }
193}
194
195impl ProcessIo {
196    /// Create captured I/O mode.
197    #[must_use]
198    pub fn captured(io: CapturedIo) -> Self {
199        Self::Captured(io)
200    }
201
202    /// Create observed I/O mode.
203    #[must_use]
204    pub fn observed(io: ObservedIo) -> Self {
205        Self::Observed(io)
206    }
207
208    /// Create inherited I/O mode.
209    #[must_use]
210    pub fn inherited(io: InheritedIo) -> Self {
211        Self::Inherited(io)
212    }
213
214    /// Create pseudoterminal I/O mode.
215    #[cfg(unix)]
216    #[must_use]
217    pub fn pty(io: PtyIo) -> Self {
218        Self::Pty(io)
219    }
220}