fire 0.6.0

Turn functions into command-line applications with one attribute, like Python's fire
Documentation

Rust Fire

Documentation

Image by T5, source: https://www.pixiv.net/artworks/104959772

Turn a Rust function or module into a command-line application with one attribute.

Installation

cargo add fire

One function

/// Welcome a person.
#[fire::main]
fn welcome(name: String, excited: bool) {
    let suffix = if excited { "!" } else { "." };
    println!("Welcome, {name}{suffix}");
}
$ app --name "John Smith" --excited
Welcome, John Smith!

#[fire::main] generates the program entry point, so no separate fn main() or registration call is needed.

Help

-h and --help are generated automatically from the function signature and documentation comments:

/// Start the HTTP server.
#[fire::main]
fn serve(
    /// Address to listen on.
    host: String,
    /// Enable verbose logging.
    verbose: bool,
) {}
$ app --help
Start the HTTP server.

Usage: app --host <HOST> [--verbose]

Options:
    --host <HOST>    Address to listen on.
    --verbose        Enable verbose logging.
    -h, --help       Print help

For module applications, app --help lists the subcommands and app <COMMAND> --help describes one subcommand.

Subcommands

Place #[fire::main] on an inline module to turn its pub functions into subcommands. Private functions stay private, so a module can keep its helpers next to its commands:

#[fire::main]
mod cli {
    pub fn hello(name: String, times: Option<u32>) {
        for _ in 0..times.unwrap_or(1) {
            println!("Hello, {name}!");
        }
    }

    pub fn bye() {
        println!("Bye!");
    }
}
$ app hello --name John --times 2
Hello, John!
Hello, John!

$ app bye
Bye!

Rust snake_case function and parameter names are exposed as CLI kebab-case names.

Parameters

The function signature defines the CLI:

Rust type CLI behavior
T required option parsed with FromStr
Option<T> optional option
bool value-less flag, defaulting to false
&str borrowed string option

Both common option formats are accepted:

$ app --name John
$ app --name=John

An option may be given only once, and a value starting with - is never taken from the next argument, so write it as --count=-1.

A command returns () or Result<_, E> where E: Display. Errors are printed to stderr and the application exits with status 2.

#[fire::main]
fn deploy(target: String) -> Result<(), DeployError> {
    do_deploy(&target)?;
    Ok(())
}

The return value is dispatched through a trait rather than by matching on the return type, so aliases such as anyhow::Result<()> work too. Any other return type is a compile error.

Async

Pass tokio to the attribute to write async commands. Each async command runs on a multi-threaded Tokio runtime, so add tokio with the rt-multi-thread feature to your application:

/// Fetch a URL.
#[fire::main(tokio)]
async fn fetch(url: String) {
    let body = reqwest::get(&url).await.unwrap();
    // ...
}

The same works on modules, and async and synchronous commands can be mixed in one module.

License

BSD-2-Clause.