5 个版本

0.2.0 2024 年 6 月 25 日
0.1.3 2024 年 6 月 19 日
0.1.2 2023 年 11 月 8 日
0.1.1 2023 年 11 月 6 日
0.1.0 2023 年 11 月 1 日

#12 in #execute

Download history 44/week @ 2024-04-25 30/week @ 2024-05-02 7/week @ 2024-05-09 13/week @ 2024-05-16 27/week @ 2024-05-23 40/week @ 2024-05-30 273/week @ 2024-06-06 242/week @ 2024-06-13 245/week @ 2024-06-20 332/week @ 2024-06-27 117/week @ 2024-07-04 16/week @ 2024-07-11 51/week @ 2024-07-18 319/week @ 2024-07-25 17/week @ 2024-08-01 52/week @ 2024-08-08

439 每月下载量
bullet_stream 中使用

MIT 许可证

33KB
445 行代码(不含注释)

Fun Run

"僵尸冲刺 5K"、"摇摆摇摆的日志慢跑"和"火鸡赛跑"有什么共同点?它们都是有有趣名字的跑步活动!这正是 fun_run 所做的。通过命名,它使得运行 Rust Command 更加有趣。

什么是 Fun Run?

Fun run 是为那些不仅想要运行 Command,还想输出正在运行的内容以及发生了什么的情况设计的。构建 CLI 工具是一个很好的用例。另一个用例是创建 构建包

以下是您可以使用 fun_run 做的一些事情

  • 在执行之前宣传正在运行的命令
  • 自定义命令的显示方式
  • 带有命令名称返回错误信息。
  • 将非零状态结果转换为错误
  • 将 stdout 和 stderr 嵌入错误(当不流式传输时)
  • 存储 stdout 和 stderr 以供调试和诊断(当流式传输时不会显示)

就像您不需要穿上巨大的火鸡服装来跑 5K 一样,您也不需要 使用 fun_run 来做这些事情。不过,与火鸡服装不同,使用 fun_run 也会使体验更加容易。

准备就绪

要快速轻松地进行 fun_run,您可以使用 fun_run::CommandWithName 特性扩展来流式传输输出

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("bundle");
cmd.args(["install"]);

// Advertise the command being run before execution
println!("Running `{name}`", name = cmd.name());

// Stream output to the end user
// Turn non-zero status results into an error
let result = cmd
    .stream_output(std::io::stdout(), std::io::stderr());

// Command name is persisted on success or failure
match result {
    Ok(output) => {
        assert_eq!("bundle install", &output.name())
    },
    Err(cmd_error) => {
        assert_eq!("bundle install", &cmd_error.name())
    }
}

漂亮的错误

fun_run 默认提供漂亮的错误

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("becho");
cmd.args(["hello", "world"]);

match cmd.stream_output(std::io::stdout(), std::io::stderr()) {
    Ok(_) => todo!(),
    Err(cmd_error) => {
        let expected = r#"Could not run command `becho hello world`. No such file or directory"#;
        let actual = cmd_error.to_string();
        assert!(actual.contains(expected), "Expected {actual:?} to contain {expected:?}, but it did not")
    }
}

并且不返回退出代码 0 的命令返回 Err,这样您就不会意外忽略失败,并且命令的输出被捕获

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("bash");
cmd.arg("-c");
cmd.arg("echo -n 'hello world' && exit 1");

// Quietly gets output
match cmd.named_output() {
    Ok(_) => todo!(),
    Err(cmd_error) => {
        let expected = r#"
Command failed `bash -c "echo -n 'hello world' && exit 1"`
exit status: 1
stdout: hello world
stderr: <empty>
        "#;

        let actual = cmd_error.to_string();
        assert!(
            actual.trim().contains(expected.trim()),
            "Expected {:?} to contain {:?}, but it did not", actual.trim(), expected.trim()
        )
    }
}

默认情况下,流式传输的输出不会在错误信息中重复(但您可以在程序中检查它)

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("bash");
cmd.arg("-c");
cmd.arg("echo -n 'hello world' && exit 1");

// Quietly gets output
match cmd.stream_output(std::io::stdout(), std::io::stderr()) {
    Ok(_) => todo!(),
    Err(cmd_error) => {
        let expected = r#"
Command failed `bash -c "echo -n 'hello world' && exit 1"`
exit status: 1
stdout: <see above>
stderr: <see above>
        "#;
        let actual = cmd_error.to_string();
        assert!(
            actual.trim().contains(expected.trim()),
            "Expected {:?} to contain {:?}, but it did not", actual.trim(), expected.trim()
        );

        let named_output: fun_run::NamedOutput = cmd_error.into();

        assert_eq!(
            "hello world",
            named_output.stdout_lossy().trim()
        );

        assert_eq!(
            "bash -c \"echo -n 'hello world' && exit 1\"",
            named_output.name()
        );
    }
}

重命名

如果您需要为您的命令提供替代显示,可以重命名它,这对于省略实现细节很有用。

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("bash");
cmd.arg("-c");
cmd.arg("echo -n 'hello world' && exit 1");

let mut renamed_cmd = cmd.named("echo 'hello world'");

assert_eq!("echo 'hello world'", &renamed_cmd.name());

这也有助于添加其他信息,例如环境变量

use fun_run::CommandWithName;
use std::process::Command;


let mut cmd = Command::new("bundle");
cmd.arg("install");

let env_vars = std::env::vars();
# let mut env_vars = std::collections::HashMap::<String, String>::new();
# env_vars.insert("RAILS_ENV".to_string(), "production".to_string());

let mut renamed_cmd = cmd.named_fn(|cmd| fun_run::display_with_env_keys(cmd, env_vars, ["RAILS_ENV"]));

assert_eq!(r#"RAILS_ENV="production" bundle install"#, renamed_cmd.name())

使用 which_problem 调试系统故障

当命令执行返回 Err,原因是因为系统错误(而不是因为它执行了程序但返回了非零状态)时,通常是因为找不到可执行文件,或者找到了但无法启动,例如由于权限错误。which_problem 包旨在添加调试错误,以帮助您确定为什么无法启动命令。

名称 which_problem 类似于 which,但有助于您识别常见错误,例如拼写错误。

$ cargo whichp zuby
Program "zuby" not found

Info: No other executables with the same name are found on the PATH

Info: These executables have the closest spelling to "zuby" but did not match:
      "hub", "ruby", "subl"

“Fun run” 通过 which_problem 功能支持 which_problem 集成。在您的 Cargo.toml 中:

# Cargo.toml
fun_run = { version = <version.here>, features = ["which_problem"] }

并标注错误。

use fun_run::CommandWithName;
use std::process::Command;

let mut cmd = Command::new("becho");
cmd.args(["hello", "world"]);

#[cfg(feature = "which_problem")]
cmd.stream_output(std::io::stdout(), std::io::stderr())
    .map_err(|error| fun_run::map_which_problem(error, cmd.mut_cmd(), std::env::var_os("PATH"))).unwrap();

现在,如果系统在您的系统上找不到 becho 程序,输出将提供您诊断潜在问题的所有信息。

请注意,which_problem 集成默认未启用,因为它会输出有关您的磁盘内容的信息,例如布局和文件权限。

它不能做什么?

fun_run 库不支持以不产生 Output 的方式执行 Command,例如调用 Command::spawn 返回一个 Result<std::process::Child, std::io::Error>(不包含 Output)。如果您想在后台运行以供娱乐,请手动创建一个线程并连接它。

use fun_run::CommandWithName;
use std::process::Command;
use std::thread;


let mut cmd = Command::new("bundle");
cmd.args(["install"]);

// Advertise the command being run before execution
println!("Quietly Running `{name}` in the background", name = cmd.name());

let result = thread::spawn(move || {
    cmd.named_output()
}).join().unwrap();

// Command name is persisted on success or failure
match result {
    Ok(output) => {
        assert_eq!("bundle install", &output.name())
    },
    Err(cmd_error) => {
        assert_eq!("bundle install", &cmd_error.name())
    }
}

FUN(功能)

如果您不想使用特质,您仍然可以通过函数映射您想要的功能来使用 fun_run

let mut cmd = std::process::Command::new("bundle");
cmd.args(["install"]);

let name = fun_run::display(&mut cmd);

cmd.output()
    .map_err(|error| fun_run::on_system_error(name.clone(), error))
    .and_then(|output| fun_run::nonzero_captured(name.clone(), output))
    .unwrap();

以下是一些可以帮助您运行的有趣函数:

依赖项

~2.1–3.5MB
~59K SLoC