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
439 每月下载量
在 bullet_stream 中使用
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();
以下是一些可以帮助您运行的有趣函数:
on_system_error
- 将std::io::Error
转换为CmdError
nonzero_streamed
- 从已流式传输到用户的Output
生成NamedOutput
nonzero_captured
- 类似于nonzero_streamed
,但用于用户尚未看到输出的情况display
- 将&mut Command
转换为人类可读的字符串display_with_env_keys
- 类似于display
,但选择性地显示环境变量
依赖项
~2.1–3.5MB
~59K SLoC