Skip to main content

pistonite_cu/process/pio/
mod.rs

1//! Utility to configure IO with child process.
2//!
3//! The `cu::pio::*` functions can be used as arguments
4//! into the [`stdin`], [`stdout`] and [`stderr`] functions
5//! of the [`Command`] builder to configure how you want to interact
6//! with the child process's IO.
7//!
8//! An input config can only be passed into [`stdin`], whereas
9//! an output config can be used both for [`stdout`] and [`stderr`].
10//! [`null`] and [`inherit`] can be used both as input and output.
11//!
12//! # Available configurations
13//! - [`cu::pio::null()`]: Turn off the stream, essentially [`Stdio::null()`].
14//! - [`cu::pio::inherit()`]: Turn off the stream, essentially [`Stdio::inherit()`].
15//!   - Note that the child outputs will mess with output from this crate.
16//! - [`cu::lv::*`]: Read the child's output and print them as a log message of the level.
17//! - [`cu::pio::spinner()`]: Show a progress bar for the child's output. Optionally also
18//!   print non-progress outputs as regular messages.
19//! - [`cu::pio::pipe()`]: Get a [`Pipe`] from the output, which can be passed as
20//!   an input config to another command to pipe the output to its stdin.
21//! - [`cu::pio::buffer()`]: Buffer the output as a `Vec<u8>`.
22//! - [`cu::pio::string()`]: Buffer the output as a `String`.
23//! - [`cu::pio::lines()`]: Get a reader that can read the output line-by-line.
24//!   - See [`cu::pio::co_lines()`] for using an async reader.
25//! - [`cu::pio::write(buf)`]: Write an in-memory buffer to child's stdin
26//!
27//!
28//! [`cu::pio::null()`]: null
29//! [`cu::pio::inherit()`]: inherit
30//! [`cu::lv::*`]: crate::lv
31//! [`cu::pio::spinner()`]: function@spinner
32//! [`cu::pio::pipe()`]: function@pipe
33//! [`cu::pio::buffer()`]: function@buffer
34//! [`cu::pio::string()`]: function@string
35//! [`cu::pio::lines()`]: function@lines
36//! [`cu::pio::co_lines()`]: function@co_lines
37//! [`cu::pio::write(buf)`]: function@write
38//! [`Command`]: super::Command
39//! [`stdin`]: super::Command::stdin
40//! [`stdout`]: super::Command::stdout
41//! [`stderr`]: super::Command::stderr
42//!
43//! # Presets
44//! Presets may apply more than just IO configuration, such as
45//! adding additional arguments or environments.
46//!
47//! - [`cu::pio::cargo()`]: Progress for a `cargo build` command.
48//!
49//! [`cu::pio::cargo()`]: function@cargo
50//!
51use std::process::Stdio;
52
53use tokio::process::{Child as TokioChild, ChildStderr, ChildStdout, Command as TokioCommand};
54
55use crate::BoxedFuture;
56
57mod pipe;
58mod read;
59mod write;
60
61#[cfg(all(feature = "print", feature = "json"))]
62mod cargo_preset;
63#[cfg(feature = "print")]
64mod print;
65#[cfg(feature = "print")]
66mod spinner;
67
68/// internal task types used in trait implementations
69pub mod config {
70    #[cfg(all(feature = "print", feature = "json"))]
71    pub use super::cargo_preset::Cargo;
72    pub use super::pipe::Pipe;
73    pub use super::read::Buffer;
74    pub use super::read::BufferString as String;
75    pub use super::read::CoLines;
76    pub use super::read::Lines;
77    #[cfg(feature = "print")]
78    pub use super::spinner::Spinner;
79    pub use super::write::Write;
80}
81
82/// internal task types used in trait implementations
83pub mod task {
84    #[cfg(all(feature = "print", feature = "json"))]
85    pub use super::cargo_preset::CargoTask as Cargo;
86    pub use super::pipe::PipeTask as Pipe;
87    #[cfg(feature = "print")]
88    pub use super::print::PrintTask as Print;
89    pub use super::read::BufferStringTask as String;
90    pub use super::read::BufferTask as Buffer;
91    pub use super::read::CoLinesTask as CoLines;
92    pub use super::read::LinesTask as Lines;
93    #[cfg(feature = "print")]
94    pub use super::spinner::SpinnerTask as Spinner;
95    pub use super::write::WriteTask as Write;
96}
97
98/// internal output types used in trait implementations
99pub mod output {
100    pub use super::pipe::PipeOutput as Pipe;
101    pub use super::read::CoLinesOutput as CoLines;
102    pub use super::read::LinesOutput as Lines;
103}
104
105// internal re-exports
106pub use output::{CoLines, Lines, Pipe};
107// factory re-exports
108#[cfg(all(feature = "print", feature = "json"))]
109pub use cargo_preset::cargo;
110pub use pipe::pipe;
111pub use read::{buffer, co_lines, lines, string};
112#[cfg(feature = "print")]
113pub use spinner::spinner;
114pub use write::write;
115
116#[cfg(feature = "print")]
117mod print_driver;
118#[cfg(feature = "print")]
119use print_driver::*;
120
121/// Configuration for process output to be used with
122/// [`Command::stdout`] and [`Command::stderr`]
123///
124/// This is essentially a marker, that is used to create tasks
125/// when spawning the child to drive the IO. See [`ChildOutTask`].
126///
127/// See [module documentation](self) for list of available configs
128///
129/// [`Command::stdout`]: crate::Command::stdout
130/// [`Command::stderr`]: crate::Command::stderr
131pub trait ChildOutConfig: Send + 'static {
132    type Task: ChildOutTask;
133    #[doc(hidden)]
134    type __Null;
135    /// Configure the standard output using this config, called before spawning
136    fn configure_stdout(&mut self, command: &mut TokioCommand);
137    /// Configure the standard error using this config, called before spawning
138    fn configure_stderr(&mut self, command: &mut TokioCommand);
139
140    // === once tokio exposes a way for us to take from StdChild, this could be
141    // used to optimize pipes
142    // /// Take the bits needed for this out config from the child, but operating on std
143    // ///
144    // /// If `Err` or `Ok(Some)` is returned, then `take()` will not be called,
145    // /// and it will be safe to implement it as `!unreachable()`
146    // fn take_std(self, _child: &mut std::process::Child, _is_out: bool) -> crate::Result<Option<Self::Task>> where Self: Sized {
147    //     Ok(None)
148    // }
149
150    /// Take the bits needed for this out config from the child
151    fn take(
152        self,
153        child: &mut TokioChild,
154        name: Option<&str>,
155        is_out: bool,
156    ) -> crate::Result<Self::Task>;
157}
158
159/// Configuration for process input to be used with
160/// [`Command::stdin`]
161///
162/// This is essentially a marker, that is used to create tasks
163/// when spawning the child to drive the IO. See [`ChildInTask`]
164///
165/// See [module documentation](self) for list of available configs
166///
167/// [`Command::stdin`]: crate::Command::stdin
168pub trait ChildInConfig: Send + 'static {
169    type Task: ChildInTask;
170    /// Configure the standard input using this config
171    fn configure_stdin(&mut self, command: &mut TokioCommand) -> crate::Result<()>;
172    /// Take the bits needed for this in config from the child
173    fn take(self, child: &mut TokioChild) -> crate::Result<Self::Task>;
174}
175
176/// Task created by [`ChildOutConfig`]
177pub trait ChildOutTask {
178    type Output: Send + 'static;
179
180    /// Run the task.
181    ///
182    /// The first return value is a future that will be spawned on the runtime
183    /// and driven internally. The output is accessible by the user.
184    fn run(self) -> (Option<BoxedFuture<()>>, Self::Output);
185}
186/// Task created by [`ChildInConfig`]
187pub trait ChildInTask {
188    /// Run the task.
189    ///
190    /// Return a future that will be spawned internally to drive writing
191    /// data to the child.
192    fn run(self) -> Option<BoxedFuture<crate::Result<()>>>;
193}
194impl ChildOutTask for () {
195    type Output = ();
196    fn run(self) -> (Option<BoxedFuture<()>>, Self::Output) {
197        (None, ())
198    }
199}
200impl ChildInTask for () {
201    fn run(self) -> Option<BoxedFuture<crate::Result<()>>> {
202        None
203    }
204}
205
206#[doc(hidden)]
207pub struct __OCNull;
208#[doc(hidden)]
209pub struct __OCNonNull;
210
211#[derive(Clone, Copy)]
212#[doc(hidden)]
213pub struct Inherit;
214/// Inherit the parent's stdin, stdout, or stderr.
215pub fn inherit() -> Inherit {
216    Inherit
217}
218impl ChildOutConfig for Inherit {
219    type Task = ();
220    type __Null = __OCNull;
221    fn configure_stdout(&mut self, command: &mut TokioCommand) {
222        command.stdout(Stdio::inherit());
223    }
224    fn configure_stderr(&mut self, command: &mut TokioCommand) {
225        command.stderr(Stdio::inherit());
226    }
227    fn take(self, _: &mut TokioChild, _: Option<&str>, _: bool) -> crate::Result<()> {
228        Ok(())
229    }
230}
231impl ChildInConfig for Inherit {
232    type Task = ();
233    fn configure_stdin(&mut self, command: &mut TokioCommand) -> crate::Result<()> {
234        command.stdin(Stdio::inherit());
235        Ok(())
236    }
237    fn take(self, _: &mut TokioChild) -> crate::Result<()> {
238        Ok(())
239    }
240}
241#[derive(Clone, Copy)]
242#[doc(hidden)]
243pub struct Null;
244/// Direct the stream to null (i.e. ignore it)
245pub fn null() -> Null {
246    Null
247}
248impl ChildOutConfig for Null {
249    type Task = ();
250    type __Null = __OCNull;
251    fn configure_stdout(&mut self, command: &mut TokioCommand) {
252        command.stdout(Stdio::null());
253    }
254    fn configure_stderr(&mut self, command: &mut TokioCommand) {
255        command.stderr(Stdio::null());
256    }
257    fn take(self, _: &mut TokioChild, _: Option<&str>, _: bool) -> crate::Result<()> {
258        Ok(())
259    }
260}
261impl ChildInConfig for Null {
262    type Task = ();
263    fn configure_stdin(&mut self, command: &mut TokioCommand) -> crate::Result<()> {
264        command.stdin(Stdio::null());
265        Ok(())
266    }
267    fn take(self, _: &mut TokioChild) -> crate::Result<()> {
268        Ok(())
269    }
270}
271
272pub(crate) fn take_child_out(
273    child: &mut TokioChild,
274    is_out: bool,
275) -> crate::Result<Result<ChildStdout, ChildStderr>> {
276    if is_out {
277        let stdout = take_child_stdout(child)?;
278        Ok(Ok(stdout))
279    } else {
280        let stderr = take_child_stderr(child)?;
281        Ok(Err(stderr))
282    }
283}
284pub(crate) fn take_child_stdout(child: &mut TokioChild) -> crate::Result<ChildStdout> {
285    let Some(stdout) = child.stdout.take() else {
286        crate::bail!(
287            "failed to take stdout from child, possibly due to conflicting IO configuration."
288        );
289    };
290    Ok(stdout)
291}
292pub(crate) fn take_child_stderr(child: &mut TokioChild) -> crate::Result<ChildStderr> {
293    let Some(stdout) = child.stderr.take() else {
294        crate::bail!(
295            "failed to take stderr from child, possibly due to conflicting IO configuration."
296        );
297    };
298    Ok(stdout)
299}