Will Nilges
Will Nilges

Reputation: 170

Parse user input String with clap for command line programming

I'd like to create a command line that utilizes clap to parse input. The best I can come up with is a loop that asks the user for input, breaks it up with a regex and builds a Vec which it somehow passes to

loop {
    // Print command prompt and get command
    print!("> "); io::stdout().flush().expect("Couldn't flush stdout");

    let mut input = String::new(); // Take user input (to be parsed as clap args)
    io::stdin().read_line(&mut input).expect("Error reading input.");
    let args = WORD.captures_iter(&input)
           .map(|cap| cap.get(1).or(cap.get(2)).unwrap().as_str())
           .collect::<Vec<&str>>();

    let matches = App::new("MyApp")
        // ... Process Clap args/subcommands
    .get_matches(args); //match arguments from CLI args variable
}

Basically, I'm wondering if there is a way to direct Clap to use a pre-given list of arguments?

Upvotes: 7

Views: 9382

Answers (3)

JoshOrndorff
JoshOrndorff

Reputation: 1701

Rather than using parse, you can use parse_from which allows you to specify the iterator.

use clap::Parser;

#[derive(Debug, Parser, Default)]
#[command(about, version, no_binary_name(true))]
struct Cli {
    #[arg(long, short, default_value_t = String::from("Default endpoint"))]
    /// RPC endpoint of the node that this wallet will connect to
    endpoint: String,

    #[arg(long, short)]
    refresh_rate: Option<u32>,
}

fn main() {
    let input = vec!["--endpoint", "localhost:8000", "--refresh-rate", "15"];

    let c = Cli::parse_from(input);

    println!("{:?}", c);
}

Upvotes: 4

Will Nilges
Will Nilges

Reputation: 170

This is how I ended up making the whole thing work:

First, I put my whole main function in a loop so that it'd be able to get commands and, well, stay in the CLI.

Next, I got input via stdin and split up the arguments

// Print command prompt and get command
print!("> ");
io::stdout().flush().expect("Couldn't flush stdout");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Error reading input.");
let args = WORD.captures_iter(&input)
           .map(|cap| cap.get(1).or(cap.get(2)).unwrap().as_str())
           .collect::<Vec<&str>>();

I then used Clap to parse, sorta like how @harmic suggested

let matches = App::new("MyApp")
    // ... command line argument options
    .get_matches_from(words);

and used subcommands instead of arguments.

eg.

.subcommand(SubCommand::with_name("list")
    .help("Print namespaces currently tracked in the database."))

The whole file is here for the curious.

Upvotes: 1

harmic
harmic

Reputation: 30597

As @mcarton says, command line programs are passed their arguments as an array, rather than a string. The shell splits the original command line (taking into account quotes, variable expansion, etc).

If your requirements are simple, you could simply split your string on whitespace and pass that to Clap. Or, if you want to respect quoted strings, you could use shellwords to parse it:

let words = shellwords::split(input)?;
let matches = App::new("MyApp")
    // ... command line argument options
    .get_matches_from(words);

Upvotes: 7

Related Questions