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, } }
2. Group related options
#![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 libraryclap_complete- Shell completion generationclap_mangen- Man page generation