Crash Courses · PhD
CLI Tool with clap
Parse arguments, read files, and ship a polished command-line app
60 min read+200 XP on completionCert: Rust Crash Course
Tap any word in the text below to start reading from there.
CLI Tool with clap
Setup
[dependencies]
clap = { version = "4", features = ["derive"] }Complete Word-Count CLI (wc clone)
use clap::Parser;
use std::{
fs::File,
io::{self, BufRead, BufReader},
path::PathBuf,
process,
};
/// A minimal wc clone — count lines, words, and chars in a file.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// File to analyse (reads stdin if omitted)
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
/// Count lines
#[arg(short = 'l', long)]
lines: bool,
/// Count words
#[arg(short = 'w', long)]
words: bool,
/// Count characters
#[arg(short = 'c', long)]
chars: bool,
}
struct Counts {
lines: usize,
words: usize,
chars: usize,
}
fn count(reader: impl BufRead) -> Counts {
let mut counts = Counts { lines: 0, words: 0, chars: 0 };
for line in reader.lines() {
match line {
Ok(l) => {
counts.lines += 1;
counts.words += l.split_whitespace().count();
counts.chars += l.chars().count() + 1; // +1 for newline
}
Err(e) => {
eprintln!("Read error: {}", e);
process::exit(1);
}
}
}
counts
}
fn main() {
let args = Args::parse();
let counts = if let Some(path) = &args.file {
match File::open(path) {
Ok(f) => count(BufReader::new(f)),
Err(e) => {
eprintln!("Error opening {:?}: {}", path, e);
process::exit(1);
}
}
} else {
count(BufReader::new(io::stdin()))
};
// If no flags, show all
let show_all = !args.lines && !args.words && !args.chars;
if args.lines || show_all { print!("{:>8}", counts.lines); }
if args.words || show_all { print!("{:>8}", counts.words); }
if args.chars || show_all { print!("{:>8}", counts.chars); }
if let Some(path) = &args.file {
println!(" {}", path.display());
} else {
println!();
}
}Build and Run
cargo build --release
# Count everything in a file
./target/release/wc Cargo.toml
# Only word count
./target/release/wc -w Cargo.toml
# From stdin
echo "hello world foo" | ./target/release/wc
# Built-in help (clap generates this automatically)
./target/release/wc --helpAdding Subcommands
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Count words in a file
Count { file: PathBuf },
/// Reverse a string
Reverse { text: String },
}Writing to Files
use std::io::Write;
use std::fs::File;
let mut file = File::create("output.txt")?;
writeln!(file, "Hello, file!")?;Reading progress
0% read
In this module
clap
#[derive(Parser)]
std::fs
BufReader
0%