CodeBlanks Logo Icon CodeBlanks

Summary - CLI with clap

Combine derive, flag and subcommand to build a file CLI.

CLI with clap summary

In clap's derive interface, structs are the argument layout: #[derive(Parser)], and field types decide the conversion. #[arg(short, long)] gives flag names, default_value_t the default; bool is a valueless flag, Option<T> optional, Vec<T> multiple positional. Subcommands are #[derive(Subcommand)] enums. Help, version and error messages are generated automatically.

Global flag and subcommand

Top-level options and the subcommand form one structure together:

#[derive(Debug)]
struct Cli {
    sessiz: bool,
    komut: Komut,
}

fn cli_ayristir(args: &[&str]) -> Result<Cli, String> {
    let sessiz = args.contains(&"--sessiz");
    let kalan: Vec<&str> = args.iter().copied().filter(|a| *a != "--sessiz").collect();
    Ok(Cli { sessiz, komut: komut_ayristir(&kalan)? })
}

Practice Full CLI

Write the Cli and cli_ayristir code above together with Komut and komut_ayristir from the previous lesson. Print the result of ["--sessiz", "sil", "2"] with {:?}.

Starter code

main.rs
#[derive(Debug)]
enum Komut {
    Ekle { metin: String },
    Liste,
    Sil { no: usize },
}

fn komut_ayristir(args: &[&str]) -> Result<Komut, String> {
    match args {
        ["ekle", metin @ ..] if !metin.is_empty() => Ok(Komut::Ekle { metin: metin.join(" ") }),
        ["liste"] => Ok(Komut::Liste),
        ["sil", n] => n
            .parse()
            .map(|no| Komut::Sil { no })
            .map_err(|_| format!("geçersiz numara: {n}")),
        [k, ..] => Err(format!("bilinmeyen alt komut: {k}")),
        [] => Err(String::from("alt komut gerekli")),
    }
}

fn main() {
    // buraya yaz
}

Sample solution

main.rs
#[derive(Debug)]
enum Komut {
    Ekle { metin: String },
    Liste,
    Sil { no: usize },
}

fn komut_ayristir(args: &[&str]) -> Result<Komut, String> {
    match args {
        ["ekle", metin @ ..] if !metin.is_empty() => Ok(Komut::Ekle { metin: metin.join(" ") }),
        ["liste"] => Ok(Komut::Liste),
        ["sil", n] => n
            .parse()
            .map(|no| Komut::Sil { no })
            .map_err(|_| format!("geçersiz numara: {n}")),
        [k, ..] => Err(format!("bilinmeyen alt komut: {k}")),
        [] => Err(String::from("alt komut gerekli")),
    }
}

#[derive(Debug)]
struct Cli {
    sessiz: bool,
    komut: Komut,
}

fn cli_ayristir(args: &[&str]) -> Result<Cli, String> {
    let sessiz = args.contains(&"--sessiz");
    let kalan: Vec<&str> = args.iter().copied().filter(|a| *a != "--sessiz").collect();
    Ok(Cli {
        sessiz,
        komut: komut_ayristir(&kalan)?,
    })
}

fn main() {
    println!("{:?}", cli_ayristir(&["--sessiz", "sil", "2"]));
}

Expected output

Ok(Cli { sessiz: true, komut: Sil { no: 2 } })

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Where does a <code>--sessiz</code> flag valid for all subcommands go?

  • In the top-level Cli struct (with #[arg(global = true)]) Correct answer
  • In each variant separately
  • In a static outside the Komut enum
  • It can't be

Explanation: global = true lets the flag be written before or after the subcommand.

2 Derive subcommands

Question
#[derive(____)]
enum Komut {
    Ekle { metin: String },
    Liste,
}
Answer
#[derive(Subcommand)]
enum Komut {
    Ekle { metin: String },
    Liste,
}

Explanation: Subcommand enums derive Subcommand.

3 Group summary

Term Meaning
#[derive(Parser)] Argument layout
bool alan Valueless flag
Option<T> alan Optional
#[derive(Subcommand)] Subcommands

All lessons in Intermediate