6

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?

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
Will Nilges
  • 160
  • 3
  • 12
  • 2
    `clap` probably doesn't have that functionality because `clap` normally doesn't get the command line as a single string, but as a list of strings, already parsed by the shell. [It is possible to ask `clap` to use a list of strings instead of using the programs' arguments.](https://docs.rs/clap/2.33.1/clap/struct.App.html#method.get_matches_from) – mcarton May 26 '20 at 21:36
  • I am not sure if I understand the question properly. Are you asking how to parse all the args passed as one single string? – Shiva May 01 '21 at 15:31

3 Answers3

6

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);
harmic
  • 28,606
  • 5
  • 67
  • 91
1

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.

Will Nilges
  • 160
  • 3
  • 12
0

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);
}
JoshOrndorff
  • 1,591
  • 9
  • 19