wang_utils_command 0.6.3

个人使用的rust工具库
Documentation
//! 异步命令
use run_script::{IoOptions, ScriptOptions};
use std::collections::HashMap;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;

/// 执行命令,同步得到结果,异步输出
///
/// # 示例:
/// ```
/// use wang_utils_command::execute_async;
///
/// #[tokio::main]
/// async fn main() -> anyhow::Result<()> {
///     // 执行命令
///     let result = execute_async(
///         "pwd && sleep 5 && pwd", // 完整命令
///         None, // 指定调用命令的目录,None为当前目录
///         |line| {
///             // 这一行会实时执行,不用等到命令执行结束
///             println!("stdout:{line}");
///         },
///         |line| {
///             // 这一行会实时执行,不用等到命令执行结束
///             println!("stderr:{line}");
///         },
///         || {
///             // 这一行会在命令执行结束后回调
///             println!("complete");
///         },
///     )
///     .await?;
///     // 会在命令执行完毕才能到这一行
///     println!("result:{result:?}");
///     Ok(())
/// }
/// ```
/// 
/// 输出结果:
/// 
/// ```text
/// stdout:/Users/wangbin/src/rust/wang-utils/examples
/// >> 中间会间隔5秒
/// stdout:/Users/wangbin/src/rust/wang-utils/examples
/// complete
/// result:true
/// ```
/// 
pub async fn execute_async(
    command: &str,
    folder: Option<&str>,
    stdout_fn: impl Fn(String) + Send + Sync + 'static,
    stderr_fn: impl Fn(String) + Send + Sync + 'static,
    complete_fn: impl Fn() + Send + Sync + 'static,
) -> anyhow::Result<bool> {
    let mut options = ScriptOptions::new();
    options.output_redirection = IoOptions::Pipe;
    options.env_vars = Some(HashMap::from([("IS_TTY".to_string(), "true".to_string())]));
    if folder.is_some() {
        options.working_directory = Some(PathBuf::from(folder.unwrap()));
    }
    let args = vec![];
    let child = run_script::spawn(command, &args, &options);

    if child.is_err() {
        let error = child.err().unwrap();
        stderr_fn(error.to_string());
        return Ok(false);
    }
    let mut child = child?;
    // 获取子进程的标准输出和标准错误
    let stdout = child.stdout.take().unwrap();
    let stderr = child.stderr.take().unwrap();

    // 在后台读取标准输出
    let handle1 = tokio::spawn(async move {
        let reader = BufReader::new(stdout);
        for line in reader.lines() {
            if let Ok(line) = line {
                stdout_fn(line);
            }
        }
    });
    // 在后台读取标准错误
    let handle2 = tokio::spawn(async move {
        let reader = BufReader::new(stderr);
        for line in reader.lines() {
            if let Ok(line) = line {
                stderr_fn(line);
            }
        }
    });
    tokio::try_join!(handle1, handle2)?;
    // 等待子进程结束
    let status = child.wait()?;
    // 检查子进程是否成功退出
    if status.success() {
        complete_fn();
        Ok(true)
    } else {
        Ok(false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use run_script::{IoOptions, ScriptOptions};
    use std::collections::HashMap;
    use std::io::{BufRead, BufReader};
    use std::path::PathBuf;

    #[tokio::test]
    async fn test_command() {
        let command = "pwd";
        let result = execute_async(
            command,
            Some("/Users/wangbin/src/docs/docs-taiwu"),
            |line| {
                println!("stdout:{line}");
            },
            |line| {
                println!("stdout:{line}");
            },
            || {
                println!("complete");
            },
        )
        .await
        .unwrap();
        println!("result:{}", result);
    }

    #[tokio::test]
    async fn test_command1() {
        let result = execute_async(
            "DOCKER_BUILDKIT=0 podman --help",
            Some("/Users/wangbin/src/docs/docs-taiwu"),
            |line| {
                println!("stdout:{line}");
            },
            |line| {
                println!("stdout:{line}");
            },
            || {
                println!("complete");
            },
        )
        .await
        .unwrap();
        println!("result:{}", result);
    }
    #[tokio::test]
    async fn test_command2() {
        let result = execute_async(
            "DOCKER_BUILDKIT=0 podman ps \"--help\"",
            Some("/Users/wangbin/src/docs/docs-taiwu"),
            |line| {
                println!("stdout:{line}");
            },
            |line| {
                println!("stdout:{line}");
            },
            || {
                println!("complete");
            },
        )
        .await
        .unwrap();
        println!("result:{}", result);
    }

    #[test]
    fn test_slice() {
        let a = "\"--help\"";
        println!("a:{a}");
        let x = &a[1..a.len() - 1];
        println!("x:{x}");
    }

    #[tokio::test]
    async fn test_run_script1() -> anyhow::Result<()> {
        let command = "antora --stacktrace antora-playbook.yml";
        let mut options = ScriptOptions::new();
        options.output_redirection = IoOptions::Pipe;
        options.env_vars = Some(HashMap::from([("IS_TTY".to_string(), "true".to_string())]));
        options.working_directory = Some(PathBuf::from("/Users/wangbin/src/docs/docs-taiwu"));
        let args = vec![];
        let result = run_script::spawn(command, &args, &options);
        println!("result");
        let mut child = result?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        // 在后台读取标准输出
        let handle1 = tokio::spawn(async move {
            let reader = BufReader::new(stdout);
            for line in reader.lines() {
                if let Ok(line) = line {
                    println!("stdout:{line}");
                }
            }
        });
        // 在后台读取标准错误
        let handle2 = tokio::spawn(async move {
            let reader = BufReader::new(stderr);
            for line in reader.lines() {
                if let Ok(line) = line {
                    println!("stderr:{line}");
                }
            }
        });
        tokio::try_join!(handle1, handle2)?;
        // 等待子进程结束
        let status = child.wait()?;
        // 检查子进程是否成功退出
        if status.success() {
            println!("success");
        } else {
            println!("error");
        }

        Ok(())
    }
    #[tokio::test]
    async fn test_run_script2() -> anyhow::Result<()> {
        let result = execute_async(
            "DOCKER_BUILDKIT=1 podman build \"--help\"",
            Some("/Users/wangbin/src/docs/docs-taiwu"),
            |line| {
                println!("stdout:{line}");
            },
            |line| {
                println!("stderr:{line}");
            },
            || {
                println!("complete");
            },
        )
        .await;
        println!("result:{result:?}");
        Ok(())
    }
}