About using a new version of a crate (exemple: clap)

Hello,
I am refactoring a code that mimics echo, from the book "Command Line in Rust".
The code in the book uses the clap 2.0 crate.
My goal is to refactor the code using the latest version of the crate (4.0.29à
I'm struggling for simple things such as getting lossy values.
More generally, how would you go about juggling between an old and a new version of a crate?

This is the code using the clap 2.0

use clap::{App,Arg};
fn main() {


    let matches = App::new("echor")
    .author("Me, saadchraibi85@mail.com")
    .version("1.0.2")
    .about("Get matches of the user")
    .arg(
     Arg::with_name("text")
      // .takes_value(true)
      .value_name("TEXT")
      .help("Input Text")
      .required(true)
      .min_values(1) // I can't find how to do this in clap = 4.x.x
    )
    .arg(
      Arg::with_name("omit-newline")
      .short("n")
      .help("Don't print newline")
      .takes_value(false)
    )
    .get_matches(); // Tell the app to parse the arguments (No one for now)

    let text = matches.values_of_lossy("text").unwrap(); // I can't find how to do this in clap = 4.x.x
   
    let omit_new_line = matches.is_present("omit-newline"); // I can't find how to do this in clap = 4.x.x

    print!("{}{}", text.join(" "), if omit_new_line { "" } else { "\n" });



}

Thanks

Most crates have relatively minor changes between their major versions. To upgrade a crate, you often just have to check the RELEASES.md file in its repository, or sometimes the GitHub release list, to see whether there are any API changes that might affect your program or library. The clap crate in particular is somewhat notorious for how much it changes between its major versions: about half of the argument functions have been replaced since Clap 2.

In this case, the best route is probably to work through the tutorial and reference for the builder functions in Clap 4. Importantly, .min_values(1) and .takes_value(false) have been replaced with the ArgAction system, and .values_of() and .is_present() have been replaced with generic .get_many() and .get_one() methods, where you have to supply the correct destination type. Overall, a translation of your program might look like this:

use clap::{Arg, ArgAction, Command};

fn main() {
    let matches = Command::new("echor")
        .author("Me, saadchraibi85@mail.com")
        .version("1.0.2")
        .about("Get matches of the user")
        .arg(
            Arg::new("text")
                .required(true)
                .action(ArgAction::Append)
                .value_name("TEXT")
                .help("Input Text"),
        )
        .arg(
            Arg::new("omit-newline")
                .short('n')
                .action(ArgAction::SetTrue)
                .help("Don't print newline"),
        )
        .get_matches();

    let text: Vec<&str> = matches
        .get_many("text")
        .unwrap()
        .map(String::as_str)
        .collect();

    let omit_new_line: bool = *matches.get_one("omit-newline").unwrap();

    print!(
        "{}{}",
        text.join(" "),
        if omit_new_line { "" } else { "\n" }
    );
}

However, this does not have the exact behavior of values_of_lossy() on invalid Unicode (which is very rare in practice on the command line). For that, you'd need to retrieve the raw arguments using ValueParser::os_string().

Variation with lossy UTF-8 parsing
use clap::{builder::ValueParser, Arg, ArgAction, Command};
use std::ffi::OsString;

fn main() {
    let matches = Command::new("echor")
        .author("Me, saadchraibi85@mail.com")
        .version("1.0.2")
        .about("Get matches of the user")
        .arg(
            Arg::new("text")
                .required(true)
                .action(ArgAction::Append)
                .value_parser(ValueParser::os_string())
                .value_name("TEXT")
                .help("Input Text"),
        )
        .arg(
            Arg::new("omit-newline")
                .short('n')
                .action(ArgAction::SetTrue)
                .help("Don't print newline"),
        )
        .get_matches();

    let text: Vec<String> = matches
        .get_many::<OsString>("text")
        .unwrap()
        .map(|s| s.to_string_lossy().into_owned())
        .collect();

    let omit_new_line: bool = *matches.get_one("omit-newline").unwrap();

    print!(
        "{}{}",
        text.join(" "),
        if omit_new_line { "" } else { "\n" }
    );
}

As a side note, newer versions of Clap have an arg!() macro which collects the functionality of many of the Arg methods. The Command can equivalently be written as (renaming omit-newline to omit_newline):

let matches = Command::new("echor")
    .author("Me, saadchraibi85@mail.com")
    .version("1.0.2")
    .about("Get matches of the user")
    .args([
        arg!(text: <TEXT> ... "Input Text"),
        arg!(omit_newline: -n "Don't print newline"),
    ])
    .get_matches();
1 Like

You don't. Upgrade is on per-crate basis, though, thus if you don't make your crates too huge it's not a big deal to have different dependencies for different crates in your codebase.