Fun Run
What does the "Zombie Zoom 5K", the "Wibbly wobbly log jog", and the "Turkey Trot" have in common?
They're runs with a fun name! That's exactly what fun_run does. It makes running your Rust Commands
more fun, by naming them.
What is Fun Run?
Fun run is designed for the use case where not only do you want to run a Command you want to
output what you're running and what happened. Building a CLI tool is a great use case. Another is
creating a buildpack.
Here's some things you can do with fun_run:
- Advertise the command being run before execution
- Customize how commands are displayed
- Return error messages with the command name.
- Turn non-zero status results into an error
- Embed stdout and stderr into errors (when not streamed)
- Store stdout and stderr for debug and diagnosis without displaying them (when streamed)
Just like you don't need to dress up in a giant turkey costume to run a 5K you also don't need
fun_run to do these things. Though, unlike the turkey costume, using fun_run will also make the
experience easier.
Install
$ cargo add fun_run
Ready to Roll
For a quick and easy fun run you can use the fun_run::CommandWithName trait extension to stream
output:
use CommandWithName;
use Command;
let mut cmd = new;
cmd.args;
// Advertise the command being run before execution
println!;
// Stream output to the end user
// Turn non-zero status results into an error
let result = cmd
.stream_output;
// Command name is persisted on success or failure
match result
Pretty (good) errors
Fun run comes with nice errors by default:
use CommandWithName;
use Command;
let mut cmd = new;
cmd.args;
let expected = r#"Could not run command `becho hello world`. No such file or directory"#;
match cmd.stream_output
And commands that don't return an exit code 0 return an Err so you don't accidentally ignore a failure, and the output of the command is captured:
use CommandWithName;
use Command;
let mut cmd = new;
cmd.arg;
cmd.arg;
// Quietly gets output
match cmd.named_output
By default, streamed output won't duplicated in error messages (but is still there if you want to inspect it in your program):
use CommandWithName;
use Command;
let mut cmd = new;
cmd.arg;
cmd.arg;
let expected = r#"
Command failed `bash -c "echo -n 'hello world' && exit 1"`
exit status: 1
stdout: <see above>
stderr: <see above>
"#;
// Quietly gets output
match cmd.stream_output
Renaming
If you need to provide an alternate display for your command you can rename it, this is useful for omitting implementation details.
use CommandWithName;
use Command;
let mut cmd = new;
cmd.arg;
cmd.arg;
let mut renamed_cmd = cmd.named;
assert_eq!;
This is also useful for adding additional information, such as environment variables:
use CommandWithName;
use Command;
let mut cmd = new;
cmd.arg;
let env_vars = vars;
let mut renamed_cmd = cmd.named_fn;
assert_eq!
Debugging system failures with which_problem
When a command execution returns an Err due to a system error (and not because the program it executed launched but returned non-zero status), it's usually because the executable couldn't be found, or if it was found, it couldn't be launched, for example due to a permissions error. The which_problem crate is designed to add debugging errors to help you identify why the command couldn't be launched.
The name which_problem works like which but helps you identify common mistakes such as typos:
$ 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 supports which_problem integration through the which_problem feature. In your Cargo.toml:
# Cargo.toml
= { = <version.here>, features = ["which_problem"] }
And annotate errors:
use CommandWithName;
use Command;
let mut cmd = new;
cmd.args;
cmd.stream_output
.map_err.unwrap;
Now if the system cannot find a becho program on your system the output will give you all the
info you need to diagnose the underlying issue.
Note that which_problem integration is not enabled by default because it outputs information
about the contents of your disk such as layout and file permissions.
What won't it do?
The fun_run library doesn't support executing a Command in ways that do not produce an
Output, for example calling Command::spawn returns a Result<std::process::Child, std::io::Error>
(Which doesn't contain an Output). If you want to run-for-fun in the background, spawn a thread
and join it manually:
use CommandWithName;
use Command;
use thread;
let mut cmd = new;
cmd.args;
// Advertise the command being run before execution
println!;
let result = spawn.join.unwrap;
// Command name is persisted on success or failure
match result
FUN(ctional)
If you don't want to use the trait, you can still use fun_run by functionally mapping the
features you want:
let mut cmd = new;
cmd.args;
let name = display;
cmd.output
.map_err
.and_then
.unwrap;
Here's some fun functions you can use to help you run:
on_system_error- Convertstd::io::ErrorintoCmdErrornonzero_streamed- Produces aNamedOutputfromOutputthat has already been streamed to the usernonzero_captured- Likenonzero_streamedbut for when the user hasn't already seen the outputdisplay- Converts an&mut Commandinto a human readable stringdisplay_with_env_keys- Likedisplaybut selectively shows environment variables.
Async
This library uses synchronous command execution. If you’re using this library in an async context, you’ll want to use an async wrapper like tokio::task::block_in_place.
Development
Update the readme:
$ cargo install cargo-rdme
$ cargo rdme