command_group/tokio.rs
1//! Implementation of process group extensions for [Tokio](https://tokio.rs)’s
2//! asynchronous [`Command` type](::tokio::process::Command).
3
4use std::{
5 io::Result,
6 process::{ExitStatus, Output},
7};
8
9use tokio::process::Command;
10
11use crate::{builder::CommandGroupBuilder, AsyncGroupChild};
12
13#[doc(inline)]
14pub use erased::ErasedChild;
15
16#[cfg(target_family = "windows")]
17mod windows;
18
19#[cfg(target_family = "unix")]
20mod unix;
21
22pub(crate) mod child;
23pub(crate) mod erased;
24
25/// Extensions for [`Command`](::tokio::process::Command) adding support for process groups.
26///
27/// This uses [`async_trait`] for now to provide async methods as a trait.
28#[async_trait::async_trait]
29pub trait AsyncCommandGroup {
30 /// Executes the command as a child process group, returning a handle to it.
31 ///
32 /// By default, stdin, stdout and stderr are inherited from the parent.
33 ///
34 /// On Windows, this creates a job object instead of a POSIX process group.
35 ///
36 /// # Examples
37 ///
38 /// Basic usage:
39 ///
40 /// ```no_run
41 /// # #[tokio::main]
42 /// # async fn main() {
43 /// use tokio::process::Command;
44 /// use command_group::AsyncCommandGroup;
45 ///
46 /// Command::new("ls")
47 /// .group_spawn()
48 /// .expect("ls command failed to start");
49 /// # }
50 /// ```
51 fn group_spawn(&mut self) -> Result<AsyncGroupChild> {
52 self.group().spawn()
53 }
54
55 /// Converts the implementor into a [`CommandGroupBuilder`](crate::CommandGroupBuilder), which can be used to
56 /// set flags that are not available on the `Command` type.
57 fn group(&mut self) -> crate::builder::CommandGroupBuilder<tokio::process::Command>;
58
59 /// Executes the command as a child process group, waiting for it to finish and
60 /// collecting all of its output.
61 ///
62 /// By default, stdout and stderr are captured (and used to provide the
63 /// resulting output). Stdin is not inherited from the parent and any
64 /// attempt by the child process to read from the stdin stream will result
65 /// in the stream immediately closing.
66 ///
67 /// On Windows, this creates a job object instead of a POSIX process group.
68 ///
69 /// # Examples
70 ///
71 /// ```should_panic
72 /// # #[tokio::main]
73 /// # async fn main() {
74 /// use tokio::process::Command;
75 /// use std::io::{self, Write};
76 /// use command_group::AsyncCommandGroup;
77 ///
78 /// let output = Command::new("/bin/cat")
79 /// .arg("file.txt")
80 /// .group_output()
81 /// .await
82 /// .expect("failed to execute process");
83 ///
84 /// println!("status: {}", output.status);
85 /// io::stdout().write_all(&output.stdout).unwrap();
86 /// io::stderr().write_all(&output.stderr).unwrap();
87 ///
88 /// assert!(output.status.success());
89 /// # }
90 /// ```
91 async fn group_output(&mut self) -> Result<Output> {
92 let child = self.group_spawn()?;
93 child.wait_with_output().await
94 }
95
96 /// Executes a command as a child process group, waiting for it to finish and
97 /// collecting its status.
98 ///
99 /// By default, stdin, stdout and stderr are inherited from the parent.
100 ///
101 /// On Windows, this creates a job object instead of a POSIX process group.
102 ///
103 /// # Examples
104 ///
105 /// ```should_panic
106 /// # #[tokio::main]
107 /// # async fn main() {
108 /// use tokio::process::Command;
109 /// use command_group::AsyncCommandGroup;
110 ///
111 /// let status = Command::new("/bin/cat")
112 /// .arg("file.txt")
113 /// .group_status()
114 /// .await
115 /// .expect("failed to execute process");
116 ///
117 /// println!("process finished with: {}", status);
118 ///
119 /// assert!(status.success());
120 /// # }
121 /// ```
122 async fn group_status(&mut self) -> Result<ExitStatus> {
123 let mut child = self.group_spawn()?;
124 child.wait().await
125 }
126}
127
128#[async_trait::async_trait]
129impl AsyncCommandGroup for Command {
130 fn group<'a>(&'a mut self) -> CommandGroupBuilder<'a, Command> {
131 CommandGroupBuilder::new(self)
132 }
133}