Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

clap

clap (Command Line Argument Parser) là một trong những library phổ biến nhất để xây dựng command-line interfaces (CLI) trong Rust. Clap giúp parse arguments, generate help messages, và validate input một cách dễ dàng.

Đặc điểm

  • 🎯 Dễ sử dụng - Derive macros cho simple APIs
  • Type-safe - Compile-time validation
  • 📝 Auto-generated help - Beautiful help messages
  • 🔧 Flexible - Builder pattern hoặc derive macros
  • 🚀 Feature-rich - Subcommands, validation, custom types...

Cài đặt

[dependencies]
clap = { version = "4.5", features = ["derive"] }

Hoặc:

cargo add clap --features derive

Ví dụ cơ bản: Derive API

use clap::Parser;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[arg(short, long)]
    name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    count: u8,
}

fn main() {
    let args = Args::parse();

    for _ in 0..args.count {
        println!("Hello {}!", args.name);
    }
}

Sử dụng:

$ cargo run -- --help
Simple program to greet a person

Usage: myapp [OPTIONS] --name <NAME>

Options:
  -n, --name <NAME>    Name of the person to greet
  -c, --count <COUNT>  Number of times to greet [default: 1]
  -h, --help          Print help
  -V, --version       Print version

$ cargo run -- --name Rust
Hello Rust!

$ cargo run -- --name Rust --count 3
Hello Rust!
Hello Rust!
Hello Rust!

Arguments Types

Required Arguments

#![allow(unused)]
fn main() {
use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Required argument
    name: String,
}
}

Optional Arguments

use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Optional argument
    #[arg(short, long)]
    name: Option<String>,
}

fn main() {
    let args = Args::parse();

    match args.name {
        Some(name) => println!("Hello {}", name),
        None => println!("Hello stranger!"),
    }
}

Flags (Boolean)

use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Enable verbose mode
    #[arg(short, long)]
    verbose: bool,

    /// Enable debug mode
    #[arg(short, long)]
    debug: bool,
}

fn main() {
    let args = Args::parse();

    if args.verbose {
        println!("Verbose mode enabled");
    }

    if args.debug {
        println!("Debug mode enabled");
    }
}

Multiple Values

use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Input files
    #[arg(short, long, num_args = 1..)]
    files: Vec<String>,
}

fn main() {
    let args = Args::parse();

    for file in args.files {
        println!("Processing: {}", file);
    }
}

Sử dụng:

$ cargo run -- --files file1.txt file2.txt file3.txt
Processing: file1.txt
Processing: file2.txt
Processing: file3.txt

Subcommands

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Add a new item
    Add {
        /// Name of the item
        name: String,
    },
    /// List all items
    List,
    /// Remove an item
    Remove {
        /// ID of the item to remove
        id: u32,
    },
}

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

    match cli.command {
        Commands::Add { name } => {
            println!("Adding: {}", name);
        }
        Commands::List => {
            println!("Listing all items...");
        }
        Commands::Remove { id } => {
            println!("Removing item with ID: {}", id);
        }
    }
}

Sử dụng:

$ cargo run -- add --help
Add a new item

Usage: myapp add <NAME>

Arguments:
  <NAME>  Name of the item

$ cargo run -- add "New Item"
Adding: New Item

$ cargo run -- list
Listing all items...

$ cargo run -- remove 123
Removing item with ID: 123

Value Validation

Possible Values

#![allow(unused)]
fn main() {
use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Log level
    #[arg(short, long, value_parser = ["debug", "info", "warn", "error"])]
    level: String,
}
}

Custom Validation

#![allow(unused)]
fn main() {
use clap::Parser;

fn validate_port(s: &str) -> Result<u16, String> {
    let port: u16 = s.parse()
        .map_err(|_| format!("`{}` isn't a valid port number", s))?;

    if port < 1024 {
        return Err("Port must be >= 1024".to_string());
    }

    Ok(port)
}

#[derive(Parser)]
struct Args {
    /// Port number (must be >= 1024)
    #[arg(short, long, value_parser = validate_port)]
    port: u16,
}
}

Range Validation

#![allow(unused)]
fn main() {
use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Number of threads (1-16)
    #[arg(short, long, value_parser = clap::value_parser!(u8).range(1..=16))]
    threads: u8,
}
}

Enum Arguments

use clap::{Parser, ValueEnum};

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
    /// Fast mode
    Fast,
    /// Normal mode
    Normal,
    /// Slow but accurate mode
    Accurate,
}

#[derive(Parser)]
struct Args {
    /// Processing mode
    #[arg(short, long, value_enum)]
    mode: Mode,
}

fn main() {
    let args = Args::parse();

    match args.mode {
        Mode::Fast => println!("Running in fast mode"),
        Mode::Normal => println!("Running in normal mode"),
        Mode::Accurate => println!("Running in accurate mode"),
    }
}

Environment Variables

#![allow(unused)]
fn main() {
use clap::Parser;

#[derive(Parser)]
struct Args {
    /// API token
    #[arg(short, long, env = "API_TOKEN")]
    token: String,
}
}

Sử dụng:

$ export API_TOKEN=abc123
$ cargo run

# Hoặc
$ cargo run -- --token abc123

Configuration Files

Kết hợp clap với config crate:

use clap::Parser;
use serde::Deserialize;

#[derive(Parser)]
struct Args {
    /// Config file path
    #[arg(short, long, default_value = "config.toml")]
    config: String,

    /// Override host from config
    #[arg(long)]
    host: Option<String>,
}

#[derive(Deserialize)]
struct Config {
    host: String,
    port: u16,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();

    // Load config file
    let config_content = std::fs::read_to_string(&args.config)?;
    let mut config: Config = toml::from_str(&config_content)?;

    // Override with CLI args if provided
    if let Some(host) = args.host {
        config.host = host;
    }

    println!("Host: {}", config.host);
    println!("Port: {}", config.port);

    Ok(())
}

Ví dụ thực tế: File processor

use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "fileproc")]
#[command(about = "A file processing tool", long_about = None)]
struct Cli {
    /// Enable verbose output
    #[arg(short, long, global = true)]
    verbose: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Convert file format
    Convert {
        /// Input file
        #[arg(short, long, value_name = "FILE")]
        input: PathBuf,

        /// Output file
        #[arg(short, long, value_name = "FILE")]
        output: PathBuf,

        /// Output format
        #[arg(short, long, value_parser = ["json", "yaml", "toml"])]
        format: String,
    },
    /// Validate file
    Validate {
        /// File to validate
        file: PathBuf,
    },
}

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

    match &cli.command {
        Commands::Convert { input, output, format } => {
            if cli.verbose {
                println!("Converting {} to {}", input.display(), output.display());
                println!("Format: {}", format);
            }
            // Conversion logic here
        }
        Commands::Validate { file } => {
            if cli.verbose {
                println!("Validating {}", file.display());
            }
            // Validation logic here
        }
    }
}

So sánh với các ngôn ngữ khác

Python (argparse)

import argparse

parser = argparse.ArgumentParser(description='Simple program to greet')
parser.add_argument('--name', type=str, required=True, help='Name to greet')
parser.add_argument('--count', type=int, default=1, help='Number of times')

args = parser.parse_args()

for _ in range(args.count):
    print(f"Hello {args.name}!")

Node.js (commander)

const { program } = require('commander');

program
  .option('--name <name>', 'Name to greet')
  .option('--count <number>', 'Number of times', 1)
  .parse();

const options = program.opts();

for (let i = 0; i < options.count; i++) {
  console.log(`Hello ${options.name}!`);
}

Ưu điểm của clap:

  • Type-safe at compile time
  • Auto-generated help messages
  • Better error messages
  • No runtime type checking needed

Best Practices

1. Sử dụng derive API cho simple cases

#![allow(unused)]
fn main() {
// ✅ Tốt - simple và clear
#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    name: String,
}
}
#![allow(unused)]
fn main() {
#[derive(Parser)]
struct Args {
    #[command(flatten)]
    database: DatabaseArgs,

    #[command(flatten)]
    server: ServerArgs,
}

#[derive(Args)]
struct DatabaseArgs {
    #[arg(long)]
    db_host: String,

    #[arg(long)]
    db_port: u16,
}

#[derive(Args)]
struct ServerArgs {
    #[arg(long)]
    server_port: u16,
}
}

3. Provide good help text

#![allow(unused)]
fn main() {
#[derive(Parser)]
#[command(about = "A comprehensive description of your tool")]
struct Args {
    /// Name of the user (used for personalization)
    #[arg(short, long, help = "Your full name")]
    name: String,
}
}

4. Use enums for fixed choices

#![allow(unused)]
fn main() {
// ✅ Tốt - type safe
#[derive(ValueEnum)]
enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
}

// ❌ Tránh - string validation at runtime
// level: String
}

Advanced Features

Custom Help Template

#![allow(unused)]
fn main() {
use clap::Parser;

#[derive(Parser)]
#[command(help_template = "\
{before-help}{name} {version}
{author-with-newline}{about-with-newline}
{usage-heading} {usage}

{all-args}{after-help}
")]
struct Args {
    name: String,
}
}

Argument Groups

#![allow(unused)]
fn main() {
use clap::{ArgGroup, Parser};

#[derive(Parser)]
#[command(group(
    ArgGroup::new("output")
        .required(true)
        .args(["json", "yaml"]),
))]
struct Args {
    #[arg(long)]
    json: bool,

    #[arg(long)]
    yaml: bool,
}
}

Tổng kết

clap là lựa chọn tốt nhất cho CLI development trong Rust:

  • ✅ Type-safe và compile-time validation
  • ✅ Auto-generated help messages
  • ✅ Flexible (derive hoặc builder API)
  • ✅ Rich feature set
  • ✅ Great error messages

Ecosystem:

  • clap - Core library
  • clap_complete - Shell completion generation
  • clap_mangen - Man page generation

References