Skip to content

Commit 4e5cf3e

Browse files
committed
Provide API to catch all output and return code
Fix #56 and also updated some doc links
1 parent 84c1cbf commit 4e5cf3e

File tree

6 files changed

+132
-89
lines changed

6 files changed

+132
-89
lines changed

README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Output will be like this:
7676
### What this library provides
7777

7878
#### Macros to run external commands
79-
- run_cmd! --> CmdResult
79+
- [run_cmd!](https://docs.rs/cmd_lib/latest/cmd_lib/macro.run_cmd.html) -> [CmdResult](https://docs.rs/cmd_lib/latest/cmd_lib/type.CmdResult.html)
8080

8181
```rust
8282
let msg = "I love rust";
@@ -101,7 +101,7 @@ run_cmd! {
101101
}?;
102102
```
103103

104-
- run_fun! --> FunResult
104+
- [run_fun!](https://docs.rs/cmd_lib/latest/cmd_lib/macro.run_fun.html) -> [FunResult](https://docs.rs/cmd_lib/latest/cmd_lib/type.FunResult.html)
105105

106106
```rust
107107
let version = run_fun!(rustc --version)?;
@@ -209,8 +209,9 @@ Ignore errors for command execution.
209209

210210
##### echo
211211
Print messages to stdout.
212-
212+
```console
213213
-n do not output the trailing newline
214+
```
214215

215216
##### error, warn, info, debug, trace
216217

@@ -227,7 +228,7 @@ run_cmd!(info "This is an infomation message")?;
227228
```
228229

229230
#### Macros to register your own commands
230-
Declare your function with `#[export_cmd(..)]` attribute, and import it with `use_custom_cmd!` macro:
231+
Declare your function with `#[export_cmd(..)]` attribute, and import it with [`use_custom_cmd!`] macro:
231232

232233
```rust
233234
#[export_cmd(my_cmd)]
@@ -244,13 +245,15 @@ println!("get result: {}", run_fun!(my_cmd)?);
244245

245246
#### Low-level process spawning macros
246247

247-
`spawn!` macro executes the whole command as a child process, returning a handle to it. By
248+
[`spawn!`] macro executes the whole command as a child process, returning a handle to it. By
248249
default, stdin, stdout and stderr are inherited from the parent. The process will run in the
249-
background, so you can run other stuff concurrently. You can call `wait()` to wait
250+
background, so you can run other stuff concurrently. You can call [`wait()`](`CmdChildren::wait()`) to wait
250251
for the process to finish.
251252

252-
With `spawn_with_output!` you can get output by calling `wait_with_output()`, or even do stream
253-
processing with `wait_with_pipe()`.
253+
With [`spawn_with_output!`] you can get output by calling [`wait_with_output()`](`FunChildren::wait_with_output()`), or even do stream
254+
processing with [`wait_with_pipe()`](`FunChildren::wait_with_pipe()`).
255+
256+
There are also other useful APIs, and you can check the docs for more details.
254257

255258
```rust
256259
let mut proc = spawn!(ping -c 10 192.168.0.1)?;
@@ -275,9 +278,9 @@ spawn_with_output!(journalctl)?.wait_with_pipe(&mut |pipe| {
275278

276279

277280
#### Macros to define, get and set thread-local global variables
278-
- `tls_init!` to define thread local global variable
279-
- `tls_get!` to get the value
280-
- `tls_set!` to set the value
281+
- [`tls_init!`] to define thread local global variable
282+
- [`tls_get!`] to get the value
283+
- [`tls_set!`] to set the value
281284
```rust
282285
tls_init!(DELAY, f64, 1.0);
283286
const DELAY_FACTOR: f64 = 0.8;
@@ -322,7 +325,7 @@ You can use the [glob](https://github.com/rust-lang-nursery/glob) package instea
322325

323326
This library tries very hard to not set global states, so parallel `cargo test` can be executed just fine.
324327
The only known APIs not supported in multi-thread environment are the
325-
`tls_init/tls_get/tls_set` macros, and you should only use them for *thread local* variables.
328+
[`tls_init`]/[`tls_get`]/[`tls_set`] macros, and you should only use them for *thread local* variables.
326329

327330

328331
License: MIT OR Apache-2.0

macros/src/lib.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream, TokenTree};
22
use proc_macro_error::{abort, proc_macro_error};
33
use quote::{quote, ToTokens};
44

5-
/// Mark main function to log error result by default
5+
/// Mark main function to log error result by default.
66
///
77
/// ```
88
/// # use cmd_lib::*;
@@ -40,7 +40,7 @@ pub fn main(
4040
.into()
4141
}
4242

43-
/// Export the function as an command to be run by `run_cmd!` or `run_fun!`
43+
/// Export the function as an command to be run by [`run_cmd!`] or [`run_fun!`].
4444
///
4545
/// ```
4646
/// # use cmd_lib::*;
@@ -79,7 +79,7 @@ pub fn export_cmd(
7979
new_functions.into()
8080
}
8181

82-
/// Import user registered custom command
82+
/// Import user registered custom command.
8383
/// ```
8484
/// # use cmd_lib::*;
8585
/// #[export_cmd(my_cmd)]
@@ -118,7 +118,7 @@ pub fn use_custom_cmd(item: proc_macro::TokenStream) -> proc_macro::TokenStream
118118
.into()
119119
}
120120

121-
/// Run commands, returning result handle to check status
121+
/// Run commands, returning [`CmdResult`](../cmd_lib/type.CmdResult.html) to check status.
122122
/// ```
123123
/// # use cmd_lib::run_cmd;
124124
/// let msg = "I love rust";
@@ -155,7 +155,7 @@ pub fn run_cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
155155
.into()
156156
}
157157

158-
/// Run commands, returning result handle to capture output and to check status
158+
/// Run commands, returning [`FunResult`](../cmd_lib/type.FunResult.html) to capture output and to check status.
159159
/// ```
160160
/// # use cmd_lib::run_fun;
161161
/// let version = run_fun!(rustc --version)?;
@@ -177,8 +177,7 @@ pub fn run_fun(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
177177
.into()
178178
}
179179

180-
/// Run commands with/without pipes as a child process, returning a handle to check the final
181-
/// result
180+
/// Run commands with/without pipes as a child process, returning [`CmdChildren`](../cmd_lib/struct.CmdChildren.html) result.
182181
/// ```
183182
/// # use cmd_lib::*;
184183
///
@@ -199,8 +198,7 @@ pub fn spawn(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
199198
.into()
200199
}
201200

202-
/// Run commands with/without pipes as a child process, returning a handle to capture the
203-
/// final output
201+
/// Run commands with/without pipes as a child process, returning [`FunChildren`](../cmd_lib/struct.FunChildren.html) result.
204202
/// ```
205203
/// # use cmd_lib::*;
206204
/// let mut procs = vec![];
@@ -231,7 +229,7 @@ pub fn spawn_with_output(input: proc_macro::TokenStream) -> proc_macro::TokenStr
231229

232230
#[proc_macro]
233231
#[proc_macro_error]
234-
/// Log a fatal message at the error level, and exit process
232+
/// Log a fatal message at the error level, and exit process.
235233
///
236234
/// e.g:
237235
/// ```

src/child.rs

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{info, warn};
1+
use crate::info;
22
use crate::{process, CmdResult, FunResult};
33
use os_pipe::PipeReader;
44
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Result};
@@ -8,7 +8,7 @@ use std::thread::JoinHandle;
88
/// Representation of running or exited children processes, connected with pipes
99
/// optionally.
1010
///
11-
/// Calling `spawn!` macro will return `Result<CmdChildren>`
11+
/// Calling [`spawn!`](../cmd_lib/macro.spawn.html) macro will return `Result<CmdChildren>`
1212
pub struct CmdChildren {
1313
children: Vec<CmdChild>,
1414
ignore_error: bool,
@@ -70,7 +70,7 @@ impl CmdChildren {
7070
/// Representation of running or exited children processes with output, connected with pipes
7171
/// optionally.
7272
///
73-
/// Calling `spawn_with_output!` macro will return `Result<FunChildren>`
73+
/// Calling [spawn_with_output!](../cmd_lib/macro.spawn_with_output.html) macro will return `Result<FunChildren>`
7474
pub struct FunChildren {
7575
children: Vec<CmdChild>,
7676
ignore_error: bool,
@@ -88,17 +88,13 @@ impl FunChildren {
8888
Err(e)
8989
}
9090
Ok(output) => {
91-
let mut s = String::from_utf8_lossy(&output).to_string();
92-
if s.ends_with('\n') {
93-
s.pop();
94-
}
9591
let ret = CmdChildren::wait_children(&mut self.children);
9692
if let Err(e) = ret {
9793
if !self.ignore_error {
9894
return Err(e);
9995
}
10096
}
101-
Ok(s)
97+
Ok(output)
10298
}
10399
}
104100
}
@@ -107,7 +103,7 @@ impl FunChildren {
107103
/// provided function.
108104
pub fn wait_with_pipe(&mut self, f: &mut dyn FnMut(Box<dyn Read>)) -> CmdResult {
109105
let child = self.children.pop().unwrap();
110-
let polling_stderr = StderrLogging::new(&child.cmd, child.stderr);
106+
let stderr_thread = StderrThread::new(&child.cmd, child.stderr, false);
111107
match child.handle {
112108
CmdChildHandle::Proc(mut proc) => {
113109
if let Some(stdout) = child.stdout {
@@ -126,10 +122,20 @@ impl FunChildren {
126122
}
127123
}
128124
};
129-
drop(polling_stderr);
125+
drop(stderr_thread);
130126
CmdChildren::wait_children(&mut self.children)
131127
}
132128

129+
/// Waits for the children processes to exit completely, returning the command result, stdout
130+
/// read result and stderr read result.
131+
pub fn wait_with_all(&mut self) -> (CmdResult, FunResult, FunResult) {
132+
// wait for the last child result
133+
let handle = self.children.pop().unwrap();
134+
let wait_all = handle.wait_with_all(true);
135+
let _ = CmdChildren::wait_children(&mut self.children);
136+
wait_all
137+
}
138+
133139
/// Returns the OS-assigned process identifiers associated with these children processes
134140
pub fn pids(&self) -> Vec<u32> {
135141
self.children.iter().filter_map(|x| x.pid()).collect()
@@ -158,8 +164,9 @@ impl CmdChild {
158164
}
159165
}
160166

161-
fn wait(self, is_last: bool) -> CmdResult {
162-
let res = self.handle.wait_with_stderr(self.stderr, &self.cmd);
167+
fn wait(mut self, is_last: bool) -> CmdResult {
168+
let _stderr_thread = StderrThread::new(&self.cmd, self.stderr.take(), false);
169+
let res = self.handle.wait(&self.cmd);
163170
if let Err(e) = res {
164171
if is_last || process::pipefail_enabled() {
165172
return Err(e);
@@ -168,27 +175,35 @@ impl CmdChild {
168175
Ok(())
169176
}
170177

171-
fn wait_with_output(self, ignore_error: bool) -> Result<Vec<u8>> {
172-
let buf = {
173-
if let Some(mut out) = self.stdout {
174-
let mut buf = vec![];
175-
if let Err(e) = out.read_to_end(&mut buf) {
176-
if !ignore_error {
177-
return Err(process::new_cmd_io_error(&e, &self.cmd));
178+
fn wait_with_output(self, ignore_error: bool) -> FunResult {
179+
let (res, stdout, _) = self.wait_with_all(false);
180+
if !ignore_error {
181+
res?;
182+
}
183+
stdout
184+
}
185+
186+
fn wait_with_all(mut self, capture: bool) -> (CmdResult, FunResult, FunResult) {
187+
let mut stderr_thread = StderrThread::new(&self.cmd, self.stderr.take(), capture);
188+
let stdout_output = {
189+
if let Some(mut out) = self.stdout.take() {
190+
let mut s = String::new();
191+
match out.read_to_string(&mut s) {
192+
Err(e) => Err(e),
193+
Ok(_) => {
194+
if s.ends_with('\n') {
195+
s.pop();
196+
}
197+
Ok(s)
178198
}
179199
}
180-
buf
181200
} else {
182-
vec![]
201+
Ok("".into())
183202
}
184203
};
185-
let res = self.handle.wait_with_stderr(self.stderr, &self.cmd);
186-
if let Err(e) = res {
187-
if !ignore_error {
188-
return Err(e);
189-
}
190-
}
191-
Ok(buf)
204+
let stderr_output = stderr_thread.join();
205+
let res = self.handle.wait(&self.cmd);
206+
(res, stdout_output, stderr_output)
192207
}
193208

194209
fn kill(self) -> CmdResult {
@@ -207,8 +222,7 @@ pub(crate) enum CmdChildHandle {
207222
}
208223

209224
impl CmdChildHandle {
210-
fn wait_with_stderr(self, stderr: Option<PipeReader>, cmd: &str) -> CmdResult {
211-
let polling_stderr = StderrLogging::new(cmd, stderr);
225+
fn wait(self, cmd: &str) -> CmdResult {
212226
match self {
213227
CmdChildHandle::Proc(mut proc) => {
214228
let status = proc.wait();
@@ -242,7 +256,6 @@ impl CmdChildHandle {
242256
}
243257
CmdChildHandle::SyncFn => {}
244258
}
245-
drop(polling_stderr);
246259
Ok(())
247260
}
248261

@@ -272,19 +285,31 @@ impl CmdChildHandle {
272285
}
273286
}
274287

275-
struct StderrLogging {
276-
thread: Option<JoinHandle<()>>,
288+
struct StderrThread {
289+
thread: Option<JoinHandle<String>>,
277290
cmd: String,
278291
}
279292

280-
impl StderrLogging {
281-
fn new(cmd: &str, stderr: Option<PipeReader>) -> Self {
293+
impl StderrThread {
294+
fn new(cmd: &str, stderr: Option<PipeReader>, capture: bool) -> Self {
282295
if let Some(stderr) = stderr {
283296
let thread = std::thread::spawn(move || {
297+
let mut output = String::new();
284298
BufReader::new(stderr)
285299
.lines()
286300
.map_while(Result::ok)
287-
.for_each(|line| info!("{}", line))
301+
.for_each(|line| {
302+
if !capture {
303+
info!("{line}");
304+
} else {
305+
output.push_str(&line);
306+
output.push('\n');
307+
}
308+
});
309+
if output.ends_with('\n') {
310+
output.pop();
311+
}
312+
output
288313
});
289314
Self {
290315
cmd: cmd.into(),
@@ -297,14 +322,28 @@ impl StderrLogging {
297322
}
298323
}
299324
}
300-
}
301325

302-
impl Drop for StderrLogging {
303-
fn drop(&mut self) {
326+
fn join(&mut self) -> FunResult {
304327
if let Some(thread) = self.thread.take() {
305-
if let Err(e) = thread.join() {
306-
warn!("[{}] logging thread exited with error: {:?}", self.cmd, e);
328+
match thread.join() {
329+
Err(e) => {
330+
return Err(Error::new(
331+
ErrorKind::Other,
332+
format!(
333+
"Running [{}] stderr thread joined with error: {e:?}",
334+
self.cmd
335+
),
336+
))
337+
}
338+
Ok(output) => return Ok(output),
307339
}
308340
}
341+
Ok("".into())
342+
}
343+
}
344+
345+
impl Drop for StderrThread {
346+
fn drop(&mut self) {
347+
let _ = self.join();
309348
}
310349
}

0 commit comments

Comments
 (0)