Command-line argument parsing

Using clap's builder API

clap clap-examples clap-github cat-command-line-interface

This application describes the structure of its command-line interface using clap⮳'s builder style. The documentation⮳ gives two other possible ways to instantiate an application.

In the builder style, with_name is the unique identifier that value_of will use to retrieve the value passed. The clap::Arg::short⮳ and clap::Arg::long⮳ options control the flag the user will be expected to type; short flags look like -f and long flags look like --file.

use std::path::PathBuf;

use clap::Arg;
use clap::Command;
use clap::value_parser;

fn cli() -> Command {
    clap::Command::new("My Test Program")
        .bin_name("test_app")
        .version("0.1.0")
        .author("Hackerman Jones <hckrmnjones@hack.gov>")
        .about("Teaches argument parsing")
        // First possible argument: --num or -n
        .arg(Arg::new("num")
                .short('n')     // -n argument
                .long("number") // --number long-form argument
                .value_name("NUMBER") // placeholder for the argument's value in the help message / usage.
                .required(false)
                .help("Enter your favorite number"))
        // Second possible argument: --file or -f
        .arg(Arg::new("file")
                .short('f')
                .long("file")
                .value_parser(value_parser!(PathBuf))
                .help("Enter the path of a file"))
    // You can also use the arg! macro: .arg(clap::arg!(-c --config <CONFIG>
    // "Optionally sets a config file to use"))
}

fn main() {
    let matches =
        cli().get_matches_from(["test_app", "-n", "42", "--file", "README.md"]);
    // In a real program, use the following to retrieve arguments from the
    // command line: let matches = cli().get_matches();

    if let Some(num) = matches.get_one::<String>("num") {
        println!("Value for num: {num}");
    }

    if let Some(file_path) = matches.get_one::<PathBuf>("file") {
        println!("Value for file: {}", file_path.display());
    }
}

Usage information is generated by clap⮳. The usage for the example application looks like this.

My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing

USAGE:
  testing [OPTIONS]

FLAGS:
  -h, --help    Prints help information
  -V, --version  Prints version information

OPTIONS:
  -f, --file <file>   A cool file
  -n, --number <num>  Five less than your favorite number

We can test the application by running a command like the following.

cargo run -- -f myfile.txt -n 251

The output is:

The file passed is: myfile.txt
Your favorite number must be 256.

Using clap's derive API

clap (tutorial) (cookbook) clap examples cat-command-line-interface

use std::path::PathBuf;

use anyhow::Result;
use clap::Parser;
use clap::Subcommand;

// The struct declaring the desired command-line arguments and
// commands

// The `derive` feature flag is required (see Cargo.toml).
#[derive(Parser, Debug)]
// Reads the following attributes the from the package's `Cargo.toml`
// Alternatively, use #[command(name = "MyApp")] ...
#[command(author, version, about, long_about = None)]
// Displays Help if no arguments are provided
#[command(arg_required_else_help = true)]
pub struct Cli {
    // Positional argument example
    /// The pattern to look for (the doc comment appears in the help)
    pattern: Option<String>,

    /// Required argument example (with default value and validation)
    #[arg(default_value_t = 8080)]
    #[arg(value_parser = clap::value_parser!(u16).range(1..))]
    port: u16,

    // Named argument example: the path to the file to look into
    #[arg(short, long)]
    path: Option<PathBuf>,

    /// Count example: turn debugging information on
    #[arg(short, long, action = clap::ArgAction::Count)]
    debug: u8,

    // // Alternatively, use the
    // // `clap-verbosity-flag` crate:
    // // It adds the following flags through the entire program:
    // // -q silences output
    // // -v show warnings
    // // -vv show info
    // // -vvv show debug
    // // -vvvv show trace
    // //  By default, this will only report errors.
    // #[clap(flatten)]
    // verbose: clap_verbosity_flag::Verbosity,

    // Subcommands
    #[command(subcommand)]
    pub command: Option<Commands>,
}

// The subcommands
#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Read something
    #[command(arg_required_else_help = true)]
    Read {
        /// A boolean flag
        #[arg(short, long)]
        all: bool,

        #[arg(required = true)]
        file: Vec<PathBuf>,
    },
    /// Say something
    Tell,
}

fn main() -> Result<()> {
    // `Clap` returns a Cli struct populated from `std::env::args_os()`...
    let cli = Cli::try_parse()?;
    // You also could use `parse()`

    // The argument values we got...
    println!("Path: {:?}", cli.path);

    // Use `unwrap_or` to set defaults for optional arguments
    // (or use `default_value_t` as above).
    println!("Pattern: {:?}", cli.pattern.unwrap_or("".to_string()));

    // You can see how many times a particular flag or argument occurred
    // Note, only flags can have multiple occurrences
    match cli.debug {
        0 => println!("Debug mode is off"),
        1 => println!("Debug mode is kind of on"),
        2 => println!("Debug mode is on"),
        _ => println!("Don't be crazy"),
    }

    // // Alternatively, use the `verbose` flag, if configured above
    // env_logger::Builder::new()
    //     .filter_level(cli.verbose.log_level_filter())
    //     .init();

    // Check for the existence of subcommands
    match &cli.command {
        Some(Commands::Read { all, file }) => {
            if *all {
                println!("Read all...");
            } else {
                println!("Read just one...");
            }
            println!("{:?}", file);
        }
        Some(Commands::Tell) => {
            println!("{}", 42);
        }
        None => {}
    }
    Ok(())
}

See also

lexopt

lexopt lexopt-crates.io lexopt-github lexopt-lib.rs

Fast compile times, fast runtime, pedantic about correctness. API is less ergonomic

fn main() {
    todo!();
}

pico-args

Fast compile times, fast runtime, more lax about correctness. API is more ergonomic

pico-args pico-args-crates.io pico-args-github pico-args-lib.rs

fn main() {
    todo!();
}