Skip to content

Commit

Permalink
Provide API to catch all output and return code
Browse files Browse the repository at this point in the history
Fix #56
  • Loading branch information
tao-guo committed Nov 19, 2023
1 parent 84c1cbf commit 6e47b16
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 64 deletions.
18 changes: 8 additions & 10 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_error::{abort, proc_macro_error};
use quote::{quote, ToTokens};

/// Mark main function to log error result by default
/// Mark main function to log error result by default.
///
/// ```
/// # use cmd_lib::*;
Expand Down Expand Up @@ -40,7 +40,7 @@ pub fn main(
.into()
}

/// Export the function as an command to be run by `run_cmd!` or `run_fun!`
/// Export the function as an command to be run by `run_cmd!` or `run_fun!`.
///
/// ```
/// # use cmd_lib::*;
Expand Down Expand Up @@ -79,7 +79,7 @@ pub fn export_cmd(
new_functions.into()
}

/// Import user registered custom command
/// Import user registered custom command.
/// ```
/// # use cmd_lib::*;
/// #[export_cmd(my_cmd)]
Expand Down Expand Up @@ -118,7 +118,7 @@ pub fn use_custom_cmd(item: proc_macro::TokenStream) -> proc_macro::TokenStream
.into()
}

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

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

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

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

#[proc_macro]
#[proc_macro_error]
/// Log a fatal message at the error level, and exit process
/// Log a fatal message at the error level, and exit process.
///
/// e.g:
/// ```
Expand Down
118 changes: 78 additions & 40 deletions src/child.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{info, warn};
use crate::info;
use crate::{process, CmdResult, FunResult};
use os_pipe::PipeReader;
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Result};
Expand All @@ -8,7 +8,7 @@ use std::thread::JoinHandle;
/// Representation of running or exited children processes, connected with pipes
/// optionally.
///
/// Calling `spawn!` macro will return `Result<CmdChildren>`
/// Calling [`spawn!`](../cmd_lib/macro.spawn.html) macro will return `Result<CmdChildren>`
pub struct CmdChildren {
children: Vec<CmdChild>,
ignore_error: bool,
Expand Down Expand Up @@ -70,7 +70,7 @@ impl CmdChildren {
/// Representation of running or exited children processes with output, connected with pipes
/// optionally.
///
/// Calling `spawn_with_output!` macro will return `Result<FunChildren>`
/// Calling [spawn_with_output!](../cmd_lib/macro.spawn_with_output.html) macro will return `Result<FunChildren>`
pub struct FunChildren {
children: Vec<CmdChild>,
ignore_error: bool,
Expand All @@ -88,17 +88,13 @@ impl FunChildren {
Err(e)
}
Ok(output) => {
let mut s = String::from_utf8_lossy(&output).to_string();
if s.ends_with('\n') {
s.pop();
}
let ret = CmdChildren::wait_children(&mut self.children);
if let Err(e) = ret {
if !self.ignore_error {
return Err(e);
}
}
Ok(s)
Ok(output)
}
}
}
Expand All @@ -107,7 +103,7 @@ impl FunChildren {
/// provided function.
pub fn wait_with_pipe(&mut self, f: &mut dyn FnMut(Box<dyn Read>)) -> CmdResult {
let child = self.children.pop().unwrap();
let polling_stderr = StderrLogging::new(&child.cmd, child.stderr);
let stderr_thread = StderrThread::new(&child.cmd, child.stderr, false);
match child.handle {
CmdChildHandle::Proc(mut proc) => {
if let Some(stdout) = child.stdout {
Expand All @@ -126,10 +122,20 @@ impl FunChildren {
}
}
};
drop(polling_stderr);
drop(stderr_thread);
CmdChildren::wait_children(&mut self.children)
}

/// Waits for the children processes to exit completely, returning the command result, stdout
/// read result and stderr read result.
pub fn wait_with_all(&mut self) -> (CmdResult, FunResult, FunResult) {
// wait for the last child result
let handle = self.children.pop().unwrap();
let wait_all = handle.wait_with_all(true);
let _ = CmdChildren::wait_children(&mut self.children);
wait_all
}

/// Returns the OS-assigned process identifiers associated with these children processes
pub fn pids(&self) -> Vec<u32> {
self.children.iter().filter_map(|x| x.pid()).collect()
Expand Down Expand Up @@ -159,7 +165,7 @@ impl CmdChild {
}

fn wait(self, is_last: bool) -> CmdResult {
let res = self.handle.wait_with_stderr(self.stderr, &self.cmd);
let res = self.handle.wait(&self.cmd);
if let Err(e) = res {
if is_last || process::pipefail_enabled() {
return Err(e);
Expand All @@ -168,27 +174,35 @@ impl CmdChild {
Ok(())
}

fn wait_with_output(self, ignore_error: bool) -> Result<Vec<u8>> {
let buf = {
if let Some(mut out) = self.stdout {
let mut buf = vec![];
if let Err(e) = out.read_to_end(&mut buf) {
if !ignore_error {
return Err(process::new_cmd_io_error(&e, &self.cmd));
fn wait_with_output(self, ignore_error: bool) -> FunResult {
let (res, stdout, _) = self.wait_with_all(false);
if !ignore_error {
res?;
}
stdout
}

fn wait_with_all(mut self, capture: bool) -> (CmdResult, FunResult, FunResult) {
let mut stderr_thread = StderrThread::new(&self.cmd, self.stderr.take(), capture);
let stdout_output = {
if let Some(mut out) = self.stdout.take() {
let mut s = String::new();
match out.read_to_string(&mut s) {
Err(e) => Err(e),
Ok(_) => {
if s.ends_with('\n') {
s.pop();
}
Ok(s)
}
}
buf
} else {
vec![]
Ok("".into())
}
};
let res = self.handle.wait_with_stderr(self.stderr, &self.cmd);
if let Err(e) = res {
if !ignore_error {
return Err(e);
}
}
Ok(buf)
let stderr_output = stderr_thread.join();
let res = self.handle.wait(&self.cmd);
(res, stdout_output, stderr_output)
}

fn kill(self) -> CmdResult {
Expand All @@ -207,8 +221,7 @@ pub(crate) enum CmdChildHandle {
}

impl CmdChildHandle {
fn wait_with_stderr(self, stderr: Option<PipeReader>, cmd: &str) -> CmdResult {
let polling_stderr = StderrLogging::new(cmd, stderr);
fn wait(self, cmd: &str) -> CmdResult {
match self {
CmdChildHandle::Proc(mut proc) => {
let status = proc.wait();
Expand Down Expand Up @@ -242,7 +255,6 @@ impl CmdChildHandle {
}
CmdChildHandle::SyncFn => {}
}
drop(polling_stderr);
Ok(())
}

Expand Down Expand Up @@ -272,19 +284,31 @@ impl CmdChildHandle {
}
}

struct StderrLogging {
thread: Option<JoinHandle<()>>,
struct StderrThread {
thread: Option<JoinHandle<String>>,
cmd: String,
}

impl StderrLogging {
fn new(cmd: &str, stderr: Option<PipeReader>) -> Self {
impl StderrThread {
fn new(cmd: &str, stderr: Option<PipeReader>, capture: bool) -> Self {
if let Some(stderr) = stderr {
let thread = std::thread::spawn(move || {
let mut output = String::new();
BufReader::new(stderr)
.lines()
.map_while(Result::ok)
.for_each(|line| info!("{}", line))
.for_each(|line| {
if !capture {
info!("{line}");
} else {
output.push_str(&line);
output.push('\n');
}
});
if output.ends_with('\n') {
output.pop();
}
output
});
Self {
cmd: cmd.into(),
Expand All @@ -297,14 +321,28 @@ impl StderrLogging {
}
}
}
}

impl Drop for StderrLogging {
fn drop(&mut self) {
fn join(&mut self) -> FunResult {
if let Some(thread) = self.thread.take() {
if let Err(e) = thread.join() {
warn!("[{}] logging thread exited with error: {:?}", self.cmd, e);
match thread.join() {
Err(e) => {
return Err(Error::new(
ErrorKind::Other,
format!(
"Running [{}] stderr thread joined with error: {e:?}",
self.cmd
),
))
}
Ok(output) => return Ok(output),
}
}
Ok("".into())
}
}

impl Drop for StderrThread {
fn drop(&mut self) {
let _ = self.join();
}
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,9 @@
pub use cmd_lib_macros::{
cmd_die, export_cmd, main, run_cmd, run_fun, spawn, spawn_with_output, use_custom_cmd,
};
/// Return type for run_fun!() macro
/// Return type for `run_fun!()` macro.
pub type FunResult = std::io::Result<String>;
/// Return type for run_cmd!() macro
/// Return type for `run_cmd!()` macro.
pub type CmdResult = std::io::Result<()>;
pub use child::{CmdChildren, FunChildren};
#[doc(hidden)]
Expand Down
18 changes: 9 additions & 9 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::thread;
const CD_CMD: &str = "cd";
const IGNORE_CMD: &str = "ignore";

/// Environment for builtin or custom commands
/// Environment for builtin or custom commands.
pub struct CmdEnv {
stdin: CmdIn,
stdout: CmdOut,
Expand All @@ -29,32 +29,32 @@ pub struct CmdEnv {
current_dir: PathBuf,
}
impl CmdEnv {
/// Returns the arguments for this command
/// Returns the arguments for this command.
pub fn args(&self) -> &[String] {
&self.args
}

/// Fetches the environment variable key for this command
/// Fetches the environment variable key for this command.
pub fn var(&self, key: &str) -> Option<&String> {
self.vars.get(key)
}

/// Returns the current working directory for this command
/// Returns the current working directory for this command.
pub fn current_dir(&self) -> &Path {
&self.current_dir
}

/// Returns a new handle to the standard input for this command
/// Returns a new handle to the standard input for this command.
pub fn stdin(&mut self) -> impl Read + '_ {
&mut self.stdin
}

/// Returns a new handle to the standard output for this command
/// Returns a new handle to the standard output for this command.
pub fn stdout(&mut self) -> impl Write + '_ {
&mut self.stdout
}

/// Returns a new handle to the standard error for this command
/// Returns a new handle to the standard error for this command.
pub fn stderr(&mut self) -> impl Write + '_ {
&mut self.stderr
}
Expand Down Expand Up @@ -83,14 +83,14 @@ pub fn export_cmd(cmd: &'static str, func: FnFun) {
CMD_MAP.lock().unwrap().insert(OsString::from(cmd), func);
}

/// Set debug mode or not, false by default
/// Set debug mode or not, false by default.
///
/// Setting environment variable CMD_LIB_DEBUG=0|1 has the same effect
pub fn set_debug(enable: bool) {
std::env::set_var("CMD_LIB_DEBUG", if enable { "1" } else { "0" });
}

/// Set pipefail or not, true by default
/// Set pipefail or not, true by default.
///
/// Setting environment variable CMD_LIB_PIPEFAIL=0|1 has the same effect
pub fn set_pipefail(enable: bool) {
Expand Down
6 changes: 3 additions & 3 deletions src/thread_local.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// Declare a new thread local storage variable
/// Declare a new thread local storage variable.
/// ```
/// # use cmd_lib::*;
/// use std::collections::HashMap;
Expand All @@ -15,7 +15,7 @@ macro_rules! tls_init {
};
}

/// Get the value of a thread local storage variable
/// Get the value of a thread local storage variable.
///
/// ```
/// # use cmd_lib::*;
Expand All @@ -35,7 +35,7 @@ macro_rules! tls_get {
};
}

/// Set the value of a thread local storage variable
/// Set the value of a thread local storage variable.
/// ```
/// # use cmd_lib::*;
/// # let changes = "";
Expand Down

0 comments on commit 6e47b16

Please sign in to comment.