简体   繁体   中英

How to make a default subcommand with clap and derive

I am trying to have a subcommand launched if there is no argument that has been supplied by the user and I could not find any way to do this.

If there is no subcommand supplied the help will show up when I want some action instead to be passed.

Based on the official clap documentation .

Modified by wrapping the Subcommand in Option , which makes it optional:

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Adds files to myapp
    Add { name: Option<String> },
}

fn main() {
    let cli = Cli::parse();

    // You can check for the existence of subcommands, and if found use their
    // matches just as you would the top level cmd
    match &cli.command {
        Some(Commands::Add { name }) => {
            println!("'myapp add' was used, name is: {:?}", name)
        }
        None => {
            println!("Default subcommand");
        }
    }
}

In the above example clap may think your would-be-argument could be a subcommand if you need options/arguments with the default one.

This can happen if the arguments are parsed separately after based on subcommand elsewhere.

This also happens with the default since the default command doesn't realise the selected variant under the subcommand that maps to further arguments therefore it has to be processed "externally".

eg. by only using Option<SubCommands> may result:

error: unrecognized subcommand 'argument'

For this you may need allow_external_subcommands with the above example:

#[command(allow_external_subcommands(true))]

Resulting the expected behaviour allowing run without the subcommand:

$ app run file

Run was used, name is: RunArgs { path: "file" }

$ app init

Init was used

$ app file

Default subcommand RunArgs { path: "file" }

However now you have another problem that you will not get informative errors when f.ex. there may not be enough arguments.

You would have to do complicated like augmenting the args:

use regex::Regex;
use std::collections::VecDeque;

// app run [OPTIONS] <file> -> explicit run
// app [OPTIONS] <file>     -> implicit run
// app <file>               -> implicit run
fn is_run_implied() -> bool {
    if std::env::args().count() < 2 {
        return false;
    }

    let test_re = Regex::new(r"^(--bench|--dir|[^\s]+\.wasm)")
        .expect("BUG: Regex error");

    test_re.is_match(&std::env::args().nth(1).unwrap())
}

fn main() {
    let augmented_args = if is_run_implied() {
        let mut augmented_args: VecDeque<String> = std::env::args().collect();
        augmented_args.insert(1, "run".to_owned());
        Args::parse_from(augmented_args)
    } else {
        Args::parse()
    };

Thinking of it, I would have thought non-exhaustive Option<CommandEnum> would have scanned only for the CommandEnum variants and nothing else as "possible" subcommands but alas not. Will have to investigate and raise a bug / feature in clap.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM