Skip to content

Commit 580ba43

Browse files
committed
Make stderr logging handle CR-separated progress output
1 parent c13a0eb commit 580ba43

File tree

2 files changed

+71
-13
lines changed

2 files changed

+71
-13
lines changed

examples/progress.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use cmd_lib::{run_cmd, CmdResult};
2+
3+
#[cmd_lib::main]
4+
fn main() -> CmdResult {
5+
run_cmd!(dd if=/dev/urandom of=/dev/null bs=1M status=progress)?;
6+
7+
Ok(())
8+
}

src/child.rs

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -342,22 +342,72 @@ struct StderrThread {
342342
impl StderrThread {
343343
fn new(cmd: &str, file: &str, line: u32, stderr: Option<PipeReader>, capture: bool) -> Self {
344344
if let Some(stderr) = stderr {
345+
let file_ = file.to_owned();
345346
let thread = std::thread::spawn(move || {
346-
let mut output = String::new();
347-
BufReader::new(stderr)
348-
.lines()
349-
.map_while(Result::ok)
350-
.for_each(|line| {
351-
if !capture {
352-
info!("{line}");
353-
} else {
354-
if !output.is_empty() {
355-
output.push('\n');
347+
if capture {
348+
let mut output = String::new();
349+
BufReader::new(stderr)
350+
.lines()
351+
.map_while(Result::ok)
352+
.for_each(|line| {
353+
if !capture {
354+
info!("{line}");
355+
} else {
356+
if !output.is_empty() {
357+
output.push('\n');
358+
}
359+
output.push_str(&line);
356360
}
357-
output.push_str(&line);
361+
});
362+
return output;
363+
}
364+
365+
// Log output one line at a time, including progress output separated by CR
366+
let mut reader = BufReader::new(stderr);
367+
let mut buffer = vec![];
368+
loop {
369+
// Unconditionally try to read more data, since the BufReader buffer is empty
370+
let result = match reader.fill_buf() {
371+
Ok(buffer) => buffer,
372+
Err(error) => {
373+
warn!("Error reading from child process: {error:?} at {file_}:{line}");
374+
break;
375+
}
376+
};
377+
// Add the result onto our own buffer
378+
buffer.extend(result);
379+
// Empty the BufReader
380+
let read_len = result.len();
381+
reader.consume(read_len);
382+
383+
// Log output. Take whole “lines” at every LF or CR (for progress bars etc),
384+
// but leave any incomplete lines in our buffer so we can try to complete them.
385+
while let Some(offset) = buffer.iter().position(|&b| b == b'\n' || b == b'\r') {
386+
let line = &buffer[..offset];
387+
let line = str::from_utf8(line).map_err(|_| line);
388+
match line {
389+
Ok(string) => info!("{string}"),
390+
Err(bytes) => info!("{bytes:?}"),
358391
}
359-
});
360-
output
392+
buffer = buffer.split_off(offset + 1);
393+
}
394+
395+
if read_len == 0 {
396+
break;
397+
}
398+
}
399+
400+
// Log any remaining incomplete line
401+
if !buffer.is_empty() {
402+
let line = &buffer;
403+
let line = str::from_utf8(line).map_err(|_| line);
404+
match line {
405+
Ok(string) => info!("{string}"),
406+
Err(bytes) => info!("{bytes:?}"),
407+
}
408+
}
409+
410+
"".to_owned()
361411
});
362412
Self {
363413
cmd: cmd.into(),

0 commit comments

Comments
 (0)