Loading page…
Rust walkthroughs
Loading page…
Clap (Command Line Argument Parser) is the most popular library for building command-line interfaces in Rust. It provides both derive and builder APIs, automatic help generation, shell completion scripts, subcommand support, and extensive validation. Clap handles argument parsing, type conversion, and error messages with minimal boilerplate.
Key concepts:
git commit, cargo buildClap generates professional CLI apps with help, version, and usage strings automatically.
# Cargo.toml
[dependencies]
clap = { version = "4", features = ["derive"] }use clap::Parser;
/// A simple calculator program
#[derive(Parser, Debug)]
#[command(name = "calc")]
#[command(author, version, about)]
struct Args {
/// First number
#[arg(short, long)]
a: i32,
/// Second number
#[arg(short, long)]
b: i32,
/// Operation to perform
#[arg(short, long, default_value = "add")]
op: String,
}
fn main() {
let args = Args::parse();
let result = match args.op.as_str() {
"add" => args.a + args.b,
"sub" => args.a - args.b,
"mul" => args.a * args.b,
"div" => args.a / args.b,
_ => panic!("Unknown operation: {}", args.op),
};
println!("Result: {}", result);
}use clap::Parser;
/// File processing tool
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Input file path
input: String,
/// Output file path
output: String,
/// Number of lines to process
#[arg(short, long, default_value = "10")]
lines: usize,
}
fn main() {
let args = Args::parse();
println!("Input: {}", args.input);
println!("Output: {}", args.output);
println!("Lines: {}", args.lines);
}
// Usage:
// ./program input.txt output.txt --lines 20
// ./program input.txt output.txt -l 20
// ./program input.txt output.txtuse clap::Parser;
/// Search tool with various options
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Search pattern
#[arg(short, long)]
pattern: String,
/// Search path (can be specified multiple times)
#[arg(short = 'p', long = "path")]
paths: Vec<String>,
/// Case insensitive search
#[arg(short, long)]
ignore_case: bool,
/// Verbose output
#[arg(short, long)]
verbose: bool,
/// Number of context lines
#[arg(short = 'C', long = "context", default_value = "2")]
context_lines: usize,
/// Output format
#[arg(short, long, value_enum, default_value = "text")]
format: OutputFormat,
}
#[derive(clap::ValueEnum, Clone, Debug, Default)]
enum OutputFormat {
Text,
Json,
Csv,
Html,
}
fn main() {
let args = Args::parse();
println!("Pattern: {}", args.pattern);
println!("Paths: {:?}", args.paths);
println!("Ignore case: {}", args.ignore_case);
println!("Verbose: {}", args.verbose);
println!("Context: {}", args.context_lines);
println!("Format: {:?}", args.format);
}
// Usage:
// ./search -p "error" --path /var/log --path ./logs -iv
// ./search --pattern "todo" --format jsonuse clap::{Parser, Subcommand};
/// Version control system
#[derive(Parser, Debug)]
#[command(name = "mygit")]
#[command(author, version, about)]
struct Cli {
/// Turn debugging information on
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Add file contents to the index
Add {
/// Files to add
#[arg(required = true)]
files: Vec<String>,
/// Add all files
#[arg(short, long)]
all: bool,
},
/// Commit changes to the repository
Commit {
/// Commit message
#[arg(short, long)]
message: String,
/// Author name override
#[arg(long)]
author: Option<String>,
},
/// Clone a repository
Clone {
/// Repository URL
url: String,
/// Target directory
#[arg(short, long)]
directory: Option<String>,
/// Clone depth
#[arg(long, default_value = "0")]
depth: usize,
},
/// Push changes to remote
Push {
/// Remote name
#[arg(default_value = "origin")]
remote: String,
/// Branch name
#[arg(default_value = "main")]
branch: String,
/// Force push
#[arg(short, long)]
force: bool,
},
/// Show working tree status
Status {
/// Show short format
#[arg(short, long)]
short: bool,
/// Show branch info
#[arg(short, long)]
branch: bool,
},
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
println!("Debug mode enabled");
}
match cli.command {
Commands::Add { files, all } => {
if all {
println!("Adding all files");
} else {
println!("Adding files: {:?}", files);
}
}
Commands::Commit { message, author } => {
println!("Commit: {}", message);
if let Some(a) = author {
println!("Author: {}", a);
}
}
Commands::Clone { url, directory, depth } => {
println!("Cloning {} (depth: {})", url, depth);
if let Some(dir) = directory {
println!("Target: {}", dir);
}
}
Commands::Push { remote, branch, force } => {
if force {
println!("Force pushing to {}/{}", remote, branch);
} else {
println!("Pushing to {}/{}", remote, branch);
}
}
Commands::Status { short, branch } => {
if short {
println!("Short status");
}
if branch {
println!("On branch: main");
}
}
}
}
// Usage:
// ./mygit add file1.txt file2.txt --all
// ./mygit commit -m "Initial commit" --author "Alice"
// ./mygit clone https://github.com/user/repo -d myrepo --depth 1
// ./mygit push origin main --force
// ./mygit status -sbuse clap::{Parser, ArgGroup};
/// Database backup tool
#[derive(Parser, Debug)]
#[command(author, version, about)]
#[command(group(
ArgGroup::new("source")
.args(["file", "database"])
.required(true)
))]
#[command(group(
ArgGroup::new("output")
.args(["output_file", "stdout"])
))]
struct Args {
/// Input file path
#[arg(short, long)]
file: Option<String>,
/// Database connection string
#[arg(short, long)]
database: Option<String>,
/// Output file path
#[arg(short, long)]
output_file: Option<String>,
/// Output to stdout
#[arg(long)]
stdout: bool,
/// Compress output
#[arg(short, long)]
compress: bool,
}
fn main() {
let args = Args::parse();
let source = args.file
.as_ref()
.map(|f| format!("file: {}", f))
.or(args.database.as_ref().map(|d| format!("database: {}", d)));
println!("Source: {}", source.unwrap());
println!("Compress: {}", args.compress);
}
// Usage:
// ./backup --file data.sql --output-file backup.sql
// ./backup --database postgres://localhost/db --stdout
// ./backup --file data.sql # Error: missing output optionuse clap::Parser;
use std::path::PathBuf;
/// File processor with validation
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Input file (must exist)
#[arg(short, long, value_parser = clap::value_parser!(PathBuf))]
input: PathBuf,
/// Output file
#[arg(short, long)]
output: PathBuf,
/// Port number (1-65535)
#[arg(short, long, value_parser = parse_port)]
port: u16,
/// IP address
#[arg(long, value_parser = parse_ip)]
ip: String,
/// Compression level (1-9)
#[arg(short, long, value_parser = 1..=9)]
level: u8,
/// Timeout in seconds
#[arg(short, long, value_parser = parse_timeout)]
timeout: u64,
}
fn parse_port(s: &str) -> Result<u16, String> {
let port: u16 = s.parse().map_err(|_| "Invalid port number".to_string())?;
if port == 0 {
Err("Port cannot be 0".to_string())
} else {
Ok(port)
}
}
fn parse_ip(s: &str) -> Result<String, String> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 4 {
return Err("Invalid IP address format".to_string());
}
for part in parts {
let n: u8 = part.parse().map_err(|_| "Invalid IP octet".to_string())?;
if n > 255 {
return Err("IP octet must be 0-255".to_string());
}
}
Ok(s.to_string())
}
fn parse_timeout(s: &str) -> Result<u64, String> {
let timeout: u64 = s.parse().map_err(|_| "Invalid timeout".to_string())?;
if timeout < 1 || timeout > 3600 {
Err("Timeout must be between 1 and 3600 seconds".to_string())
} else {
Ok(timeout)
}
}
fn main() {
let args = Args::parse();
println!("Input: {:?}", args.input);
println!("Output: {:?}", args.output);
println!("Port: {}", args.port);
println!("IP: {}", args.ip);
println!("Level: {}", args.level);
println!("Timeout: {}s", args.timeout);
}use clap::Parser;
/// Configuration tool with environment variable support
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// API key (from --api-key or MYAPP_API_KEY env var)
#[arg(long, env = "MYAPP_API_KEY")]
api_key: String,
/// Server host
#[arg(long, env = "MYAPP_HOST", default_value = "localhost")]
host: String,
/// Server port
#[arg(long, env = "MYAPP_PORT", default_value = "8080")]
port: u16,
/// Log level
#[arg(long, env = "MYAPP_LOG_LEVEL", default_value = "info")]
log_level: String,
/// Database URL (optional env var)
#[arg(long, env = "DATABASE_URL")]
database_url: Option<String>,
}
fn main() {
let args = Args::parse();
println!("API Key: {}", args.api_key);
println!("Host: {}", args.host);
println!("Port: {}", args.port);
println!("Log Level: {}", args.log_level);
if let Some(url) = args.database_url {
println!("Database: {}", url);
}
}
// Usage:
// MYAPP_API_KEY=secret ./app
// ./app --api-key secret --port 3000
// DATABASE_URL=postgres://localhost/db ./app --api-key secretuse clap::Parser;
/// A modern file search tool
#[derive(Parser, Debug)]
#[command(
name = "fsearch",
author,
version,
about,
long_about = "A fast, modern file search tool with regex support.",
next_line_help = true,
disable_help_flag = false,
disable_version_flag = false,
)]
struct Args {
/// Search pattern (supports regex)
#[arg(short, long, help = "Pattern to search for in file contents")]
pattern: String,
/// Root directory to search
#[arg(short, long, default_value = ".", help = "Starting directory")]
path: String,
/// File extension filter
#[arg(short = 'e', long, help = "Only search files with this extension")]
extension: Option<String>,
/// Case insensitive search
#[arg(short, long, help_heading = "Search Options")]
ignore_case: bool,
/// Show line numbers
#[arg(short = 'n', long, help_heading = "Output Options")]
line_numbers: bool,
/// Show file names only
#[arg(short, long, help_heading = "Output Options")]
files_with_matches: bool,
/// Recursive search depth
#[arg(long, default_value = "10", help_heading = "Search Options")]
max_depth: usize,
/// Exclude patterns
#[arg(short, long, help_heading = "Filter Options")]
exclude: Vec<String>,
}
fn main() {
let args = Args::parse();
println!("Pattern: {}", args.pattern);
println!("Path: {}", args.path);
if let Some(ext) = args.extension {
println!("Extension: {}", ext);
}
println!("Max depth: {}", args.max_depth);
if !args.exclude.is_empty() {
println!("Exclude: {:?}", args.exclude);
}
}use clap::{Parser, Subcommand};
/// Docker-like CLI with nested commands
#[derive(Parser, Debug)]
#[command(name = "container")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Manage containers
Container {
#[command(subcommand)]
command: ContainerCommands,
},
/// Manage images
Image {
#[command(subcommand)]
command: ImageCommands,
},
/// Manage networks
Network {
#[command(subcommand)]
command: NetworkCommands,
},
}
#[derive(Subcommand, Debug)]
enum ContainerCommands {
/// List containers
Ls {
/// Show all containers (including stopped)
#[arg(short, long)]
all: bool,
/// Filter by name
#[arg(short, long)]
filter: Option<String>,
},
/// Run a container
Run {
/// Image name
image: String,
/// Container name
#[arg(short, long)]
name: Option<String>,
/// Port mapping (e.g., 8080:80)
#[arg(short, long)]
publish: Vec<String>,
/// Environment variables
#[arg(short, long)]
env: Vec<String>,
/// Run in background
#[arg(short, long)]
detach: bool,
},
/// Stop a container
Stop {
/// Container ID or name
containers: Vec<String>,
/// Time to wait before killing
#[arg(short, long, default_value = "10")]
time: u32,
},
/// Remove a container
Rm {
/// Container ID or name
containers: Vec<String>,
/// Force removal
#[arg(short, long)]
force: bool,
},
}
#[derive(Subcommand, Debug)]
enum ImageCommands {
/// List images
Ls,
/// Pull an image
Pull {
/// Image name
name: String,
/// Tag
#[arg(short, long, default_value = "latest")]
tag: String,
},
/// Remove an image
Rm {
/// Image ID or name
images: Vec<String>,
},
}
#[derive(Subcommand, Debug)]
enum NetworkCommands {
/// List networks
Ls,
/// Create a network
Create {
/// Network name
name: String,
/// Network driver
#[arg(long, default_value = "bridge")]
driver: String,
},
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Container { command } => match command {
ContainerCommands::Ls { all, filter } => {
println!("Listing containers (all: {})", all);
if let Some(f) = filter {
println!("Filter: {}", f);
}
}
ContainerCommands::Run { image, name, publish, env, detach } => {
println!("Running image: {}", image);
if let Some(n) = name {
println!("Name: {}", n);
}
println!("Ports: {:?}", publish);
println!("Env: {:?}", env);
if detach {
println!("Running in background");
}
}
ContainerCommands::Stop { containers, time } => {
println!("Stopping {:?} (timeout: {}s)", containers, time);
}
ContainerCommands::Rm { containers, force } => {
if force {
println!("Force removing {:?}", containers);
} else {
println!("Removing {:?}", containers);
}
}
},
Commands::Image { command } => match command {
ImageCommands::Ls => println!("Listing images"),
ImageCommands::Pull { name, tag } => {
println!("Pulling {}: {}", name, tag);
}
ImageCommands::Rm { images } => {
println!("Removing images: {:?}", images);
}
},
Commands::Network { command } => match command {
NetworkCommands::Ls => println!("Listing networks"),
NetworkCommands::Create { name, driver } => {
println!("Creating network '{}' with driver '{}'", name, driver);
}
},
}
}
// Usage:
// ./container container ls --all
// ./container container run nginx -d -p 8080:80 --name web
// ./container image pull alpine --tag 3.18
// ./container network create mynet --driver bridge#[derive(Parser)] on a struct for the derive API (recommended)#[arg(short, long)] creates -s and --short flags/optionsOption<T> for optional arguments, Vec<T> for multiple values#[arg(default_value = "...")] sets a default#[command(subcommand)] with #[derive(Subcommand)] enum creates subcommands#[arg(value_parser = ...)] for custom validation functions#[arg(env = "VAR")] reads from environment variables#[arg(global = true)] makes flags available to all subcommandsArgGroup with #[command(group(...))] for mutually exclusive arguments#[arg(help = "...")] customizes help text#[command(help_heading = "...")] groups options in help output#[derive(ValueEnum)] creates enumerated values for argumentsclap::value_parser!(PathBuf) for type-safe path parsing--help and --version flags are generated automatically