Nathaniel Ford
Nathaniel Ford

Reputation: 21239

Parse Structopt flag from anywhere in the input

I would like to be able to have 'universal' flags in my command line tool, which uses StructOpt. That is, if I have a flag (such as --debug) I want it to behave the same regardless of where that flag is in the input:

$ mycli --debug alpha
$ mycli alpha --debug

(alpha and --debug are simply useful placeholders in this example; actual subcommands and flags would be different.)

Actual behavior:

$ cargo run -- --debug alpha                                                                                               nford 14:36:32
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/scratch-rust --debug alpha`
Debug mode enabled!
Alpha subcommand selected.
$ cargo run -- alpha --debug                                                                                               nford 14:36:27
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/scratch-rust alpha --debug`
error: Found argument '--debug' which wasn't expected, or isn't valid in this context

USAGE:
    scratch-rust alpha

Expected Behavior:

$ cargo run -- --debug alpha                                                                                               nford 14:36:32
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/scratch-rust --debug alpha`
Debug mode enabled!
Alpha subcommand selected.
$ cargo run -- alpha --debug       
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/scratch-rust --debug alpha`
Debug mode enabled!
Alpha subcommand selected.

This is my main.rs code:

use std::str::FromStr;
use structopt::StructOpt;
use std::io::{Error, ErrorKind};

#[derive(Debug, StructOpt)]
/// Specific task being executed; one task might imply others, depending on flags.
pub enum Subcommand {
    Alpha,
    Omega,
}

impl FromStr for Subcommand {
    type Err = Error;
    fn from_str(subcommand_input: &str) -> Result<Self, Self::Err> {
        match subcommand_input {
            "alpha" => Ok(Subcommand::Alpha),
            "omega" => Ok(Subcommand::Omega),
            _ => Err(Error::new(ErrorKind::Other, "Unrecognized subcommand.")),
        }
    }
}

#[derive(Debug, StructOpt)]
#[structopt(
    name = "Minimal Example CLI",
    about = "A minimal example of multi-location command line inputs.",
)]
pub struct CLIOpts {
    /// Set logging to verbose mode.
    // short and long flags (-d, --debug) will be deduced from the field's name
    #[structopt(short, long)]
    pub debug: bool,

    #[structopt(subcommand)]
    pub cmd: Subcommand,
}


fn main() {
    let args = CLIOpts::from_args();
    if args.debug {
        println!("Debug mode enabled!");
    }
    match args.cmd {
        Subcommand::Alpha => println!("Alpha subcommand selected."),
        Subcommand::Omega => println!("Omega subcommand selected."),
    }
}

This is my Cargo.toml file (the above example won't work w/o the structopt dependency):

[package]
name = "scratch-rust"
version = "0.1.0"
edition = "2018"

[dependencies]
structopt = "*"

Is there a way to do this without replicating the 'universal' flags at every level of subcommand?

Upvotes: 1

Views: 384

Answers (1)

Elias Holzmann
Elias Holzmann

Reputation: 3679

Clap has a global() method for arguments that does what you want. You can access this method vis Structopt using raw attributes:

pub struct CLIOpts {
    /// Set logging to verbose mode.
    // short and long flags (-d, --debug) will be deduced from the field's name
    #[structopt(short, long, global = true)]
    pub debug: bool,

    #[structopt(subcommand)]
    pub cmd: Subcommand,
}

(note the added global attribute for debug)

The application now works the way you want it to:

$ cargo run -- --debug alpha
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/scratch-rust --debug alpha`
Debug mode enabled!
Alpha subcommand selected.
$ cargo run -- alpha --debug
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/scratch-rust alpha --debug`
Debug mode enabled!
Alpha subcommand selected.

Upvotes: 1

Related Questions